题目大意:有\(n\)个储钱罐,每个储钱罐的钥匙都在另一个储钱罐中。求打开每一个储钱罐所需要砸开的最小数量。
\(Solution\):如果储钱罐\(x\)的钥匙在\(y\)中,那么就连边\(x\)->\(y\)。于是每个点的出度都是1。然后就变成了和NOIP 2015 Day1 T2几乎一样的题——Tarjan缩点或者DFS,统计强连通块数量即可。
——但是本题会卡内存,所以这么做会爆栈,MLE妥妥的。所以参考了网上的一种用并查集的“优越做法”。注意到每个点都在且仅在一个强连通分量(也就是若干个无交点的简单环),所以可以把它们直接当成连通分量做,并查集维护之即可。
// BZOJ 1529
#include <cstdio>
#include <cstring>
using namespace std;
#define N 1000005
int fa[N], n, y;
int find(int x) { return fa[x]==x ? x : fa[x]=find(fa[x]); }
#define read(x) scanf("%d", &x)
#define rep(i,a,b) for (int i=a; i<=b; i++)
int main()
{
read(n);
int ans=n;
rep(i,1,n) fa[i]=i;
rep(x,1,n) {
read(y);
int fx=find(x), fy=find(y);
if (fx!=fy) ans--;
fa[fx]=fy;
}
printf("%d\n", ans);
return 0;
}