Codeforces 699D Fix a Tree 并查集
原题:http://codeforces.com/contest/699/problem/D
题目中所描述的从属关系,可以看作是一个一个块,可以用并查集来维护这个森林。这些从属关系中会有两种环,第一种是一个点从自身出发到自己,这说明该点是一棵子树的根;第二种是从一点出发到另外一个点。这两种情况在并查集合并的时候都会失败,因为合并时他们都已经属于一个子树,我们现在需要做的就是将这些子树合并,这时我们要优先对生成第二种环的子树进行合并,因为这些从属关系一定是需要修改的,第一种情况有一个点可以不需要修改,作为最后合并好的树的根节点。最后,总的操作数等于子树的个数减一,因为最后合并的树的根节点不需要修改。
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 222222; 4 int a[maxn]; 5 int f[maxn];//维护并查集 6 int book[maxn];//标记,为1 的点输出改变后的值,为0输出原值 7 int find(int x){ 8 if(f[x] == x) 9 return x; 10 return f[x] = find(f[x]); 11 } 12 int merge(int x,int y){ 13 int u = find(x); 14 int v = find(y); 15 if(u != v){ 16 f[v] = u; 17 return 1; 18 } 19 return 0; 20 } 21 int main(){ 22 int n; 23 scanf("%d",&n); 24 //并查集初始化 25 for(int i = 1;i<=n;i++){ 26 scanf("%d",&a[i]); 27 f[i] = i; 28 } 29 int cnt = 0;//操作计数 30 int pre = -1;//记录需要合并的前一个点 31 for(int i = 1;i<=n;i++){ 32 //对第二种情况的点进行合并 33 if(!merge(a[i],i) && a[i]!=i){ 34 cnt++; 35 if(pre != -1){ 36 merge(i,pre); 37 } 38 pre = i; 39 book[i] = 1; 40 } 41 } 42 //对第一种情况进行合并 43 for(int i = 1;i<=n;i++){ 44 if(i == f[i]){ 45 cnt++; 46 book[i] = 1; 47 if(pre != -1){ 48 merge(i,pre); 49 } 50 pre = i; 51 } 52 } 53 printf("%d\n",cnt-1); 54 for(int i = 1;i<=n;i++){ 55 if(book[i]) 56 printf("%d ",f[i]); 57 else 58 printf("%d ",a[i]); 59 } 60 return 0; 61 }