题意:上帝手中有 N(N≤10^6) 种世界元素,每种元素可以限制另外1种元素,把第 i 种世界元素能够限制的那种世界元素记为 A[i]。现在,上帝要把它们中的一部分投放到一个新的空间中去建造世界。为了世界的和平与安宁,上帝希望所有被投放的世界元素都有至少一个没有被投放的世界元素限制它。上帝希望知道,在此前提下,他最多可以投放多少种世界元素?
分析:
方法一:入度为0的点x,由于x无法被控制,所以只能不选。选择x控制的节点a[x]投放一定是最优的。 选择a[x]之后,a[a[x]]就不能被a[x]限制了,把他的度数-1,如果a[a[x]]的度数=0,说明他也可以去限制别人了,把他加入待转移集合中(本质上就是拓扑)。环上的点是不会被加入集合的,对于一个长度为cnt的环,可以选择投放的点为cnt/2个(隔一个选一个),求出每一颗内向树环的长度即可。参考https://blog.csdn.net/CABI_ZGX/article/details/83501714。
方法二:基环树dp,详见《算法竞赛进阶指南》P389,不过有些细节还没搞透,还要回过头来再看。
代码一(方法一):
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+10;
int n,a[N],du[N],q[N],l,r;
bool v[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
du[a[i]]++;
}
l=1; r=0;
for(int i=1;i<=n;i++)
if(du[i]==0)
q[++r]=i;
int ans=0;
while(l<=r){
int x=q[l];
if(!v[x] && !v[a[x]]){
ans++;
v[a[x]]=1;
du[a[a[x]]]--; if(du[a[a[x]]]==0) q[++r]=a[a[x]];
}
v[q[l]]=1; l++;
}
int cnt=0,j;
for(int i=1;i<=n;i++){
if(!v[i]){
cnt=0;
j=i;
while(a[j]!=i){
v[j]=1;
cnt++;
j=a[j];
}
v[j]=1;
ans+=(cnt+1)/2;
}
}
printf("%d\n",ans);
return 0;
}
代码二(方法二):
#include<bits/stdc++.h>
using namespace std;
const int N = 1000006, INF = 0x3f3f3f3f;
int n, fa[N], t, k, f[N][2], s[N][2], ans;
int Head[N], Edge[N<<1], Next[N<<1], tot;
int get(int x) {
if (x == fa[x]) return x;
return fa[x] = get(fa[x]);
}
inline void add(int x, int y) {
Edge[++tot] = y;
Next[tot] = Head[x];
Head[x] = tot;
}
void dfs(int x) {
int num = INF;
f[x][0] = 0;
for (int i = Head[x]; i; i = Next[i]) {
if (Edge[i] != k) dfs(Edge[i]);
f[x][0] += max(f[Edge[i]][0], f[Edge[i]][1]);
num = min(num, max(f[Edge[i]][0], f[Edge[i]][1]) - f[Edge[i]][0]);
}
f[x][1] = f[x][0] + 1 - num;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
int p = get(x), q = get(i);
if (p == q) {
s[++t][0] = x;
s[t][1] = i;
} else {
add(x, i);
fa[q] = p;
}
}
for (int i = 1; i <= t; i++) {
k = 0;
dfs(s[i][0]);
k = s[i][0];
dfs(s[i][1]);
int now = f[s[i][1]][1];
f[s[i][0]][1] = f[s[i][0]][0] + 1;///强制连接 p和A[p]
dfs(s[i][1]);
ans += max(now, f[s[i][1]][0]);
}
cout << ans << endl;
return 0;
}