N个狙击手在进行生死搏斗,每位狙击手各瞄准了一个目标,一旦开枪就会将被瞄准的目标消灭,被消灭的狙击手无法再开枪。已知任意两个狙击手不会同时开枪,但不知道他们的开枪顺序,所以无法确定最后哪些狙击手会存活下来。
请你求出最少可能存活多少个狙击手,最多可能存活多少个狙击手。
第一行一个正整数N 接下来一行N个正整数,第i个数字Ti表示第i个狙击手瞄准了第Ti个狙击手,注意可能存在Ti=i
两行,第一行是最少存活的狙击手的数量,第二行是最多存活的狙击手的数量
8 2 3 2 2 6 7 8 5
3 5
样例解释: 最少存活的情况:2开枪消灭3,1开枪消灭2,7开枪消灭8,6开枪消灭7,5开枪消灭6,最后1, 4, 5存活。 最多存活的情况:1开枪消灭2,5开枪消灭6,7开枪消灭8,最后1,3,4,5,7存活。 数据约定: 20% N≤10 50% N≤10000 100% N≤1000000,1≤Ti≤N
import java.util.*;
// 初始入度0的必活、自环必死 🔥
// 最大存活:初始入度0的开枪后, 目标死, 目标的目标入度-1, 类似拓扑排序, 其他环存活一半(向下取整5/2=1)
// 最小存活:初始入度0指向的环存活0个, 其他环存活1个
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] target = new int[n + 1]; // 瞄准目标
int[] indeg = new int[n + 1]; // 入度
for (int i = 1; i <= n; i++) {
int t = in.nextInt();
target[i] = t;
indeg[t]++;
}
int max = getMax(target, indeg.clone(), n); // clone副本, 函数里要修改内容
int min = getMin(target, indeg.clone(), n);
System.out.printf("%d\n%d", min, max);
}
// 最多存活
static int getMax(int[] target, int[] indeg, int n) {
int res = 0;
Deque<Integer> q = new ArrayDeque<>();
boolean[] used = new boolean[n + 1];
for (int i = 1; i <= n; i++) {
if (indeg[i] == 0 || target[i] == i) { // 初始入度0 || 自环
used[i] = true;
if (indeg[i] == 0) {
q.offer(i);
res++;
}
}
}
while (!q.isEmpty()) { // 初始入度0的开枪, 可能产生新的入度0
int i = q.poll();
if (!used[target[i]]) { // if 仅下个目标
used[target[i]] = true;
int t = target[target[i]];
if (--indeg[t] == 0) {
used[t] = true;
q.offer(t);
res++;
}
}
}
for (int i = 1; i <= n; i++) { // 剩余的环:每个环最多活size/2个
if (used[i]) continue;
int t = i, size = 0;
while (!used[t]) {
size++;
used[t] = true;
t = target[t];
}
res += size / 2;
}
return res;
}
// 最少存活
static int getMin(int[] target, int[] indeg, int n) {
int res = 0;
Deque<Integer> q = new ArrayDeque<>();
boolean[] used = new boolean[n + 1];
for (int i = 1; i <= n; i++) {
if (indeg[i] == 0 || target[i] == i) { // 初始入度0 || 自环
used[i] = true;
if (indeg[i] == 0) {
q.offer(i);
res++;
}
}
}
while (!q.isEmpty()) { // 初始入度0指向的环全灭
int i = q.poll();
while (!used[target[i]]) { // while 处理环
used[target[i]] = true;
i = target[i];
}
}
for (int i = 1; i <= n; i++) { // 剩余的环:每个环最多活1个
if (used[i]) continue;
int t = i;
while (!used[t]) {
used[t] = true;
t = target[t];
}
res++;
}
return res;
}
}