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
",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 }
原文地址:https://www.cnblogs.com/zqy123/p/5690790.html