并查集总结篇

来源:互联网 发布:剑三儒风盾太数据 编辑:程序博客网 时间:2024/06/03 20:03

转载,原创请访问:http://blog.csdn.net/zhou_yujia/article/details/51392052

1、模板题 poj1611the suspects

每个组内的人,同一个组内都是感染者,问与“0”号人有关的有多少人

[cpp] view plain copy print?
  1. #include <iostream>  
  2. #include<cstdio>  
  3. using namespace std;  
  4. const int MAXN = 1000100;  
  5. struct DS  
  6. {  
  7.     int f[MAXN];  
  8.     void init(int n)  
  9.     {  
  10.         for(int i=0;i<n;i++)  
  11.             f[i]=i;  
  12.     }  
  13.     int ff(int x)               ///int father (a);  
  14.     {  
  15.         ///查找父亲并压缩路径  
  16.         if(f[x]!=x)  
  17.             f[x]=ff(f[x]);  
  18.         return f[x];  
  19.     }  
  20.     void join(int a,int b)      ///void union (a, b);  
  21.     {  
  22.         f[ff(a)]=f[ff(b)];  
  23.     }  
  24.     int find(int a,int b)  
  25.     {  
  26.         return ff(a)==ff(b);  
  27.     }  
  28. }soul;  
  29.   
  30. int main()  
  31. {  
  32.    // freopen("cin.txt","r",stdin);  
  33.     int n,m,a,b,c,t,num[100000];  
  34.     while(cin>>n>>m)  
  35.     ///n:人数    m:操作数  
  36.     {  
  37.         if(m==0&&n==0) break;  
  38.         soul.init(n);  
  39.         while(m--)  
  40.         {  
  41.             cin>>t;  
  42.             for(int i=1;i<=t;i++) cin>>num[i];  
  43.             for(int i=1;i<t;i++)  
  44.              soul.join(num[i],num[i+1]);  
  45.         }  
  46.         int q=soul.ff(0);  
  47.         int count=1;  
  48.         for(int i=1;i<n;i++)  
  49.          if(q==soul.ff(i)) count++;  
  50.         cout<<count<<endl;  
  51.     }  
  52.     return 0;  
  53. }  

2、初级带权并查集
poj2492bug's life

题意:给出每对虫子的互相吸引关系,只有异性才会相互吸引,问虫子当中有没有同性恋==

解决方法:添加一个数组表示每个虫子的性别,注释加讲解

[cpp] view plain copy print?
  1. /*  
  2. 关于并查集,注意两个概念:按秩合并、路径压缩。 
  3. 1、按秩合并 
  4. 由于并查集一般是用比较高效的树形结构来表示的,按秩合并的目的就是防止产生退化的树(也就是类似链表的树), 
  5. 用一个数组记录各个元素的高度(也有的记录各个元素的孩子的数目,具体看哪种能给解题带来方便), 
  6. 然后在合并的时候把高度小的嫁接到高度大的上面,从而防止产生退化的树。 
  7. 2、路径压缩 
  8. 而另一个数组记录各个元素的祖先,这样就防止一步步地递归查找父亲从而损失的时间。因为并查集只要搞清楚各个元素所在的集合, 
  9. 而区分不同的集合我们用的是代表元素(也就是树根),所以对于每个元素我们只需保存其祖先,从而区分不同的集合。 
  10. 而我们这道题并没有使用纯正的并查集算法,而是对其进行了扩展, 
  11. 我们并没有使用“1、按秩合并”(当然你可以用,那样就需要再开一个数组) 
  12. 我们从“1、按秩合并”得到启示,保存“秩”的数组保存的是    元素相对于父节点的关系  ,我们岂不可以利用这种关系 
  13. (即相对于父节点的不同秩值来区分不同的集合),从而可以把两个集合合并成一个集合。 
  14. (注:此代码 relation=0 代表 和父节点同一性别) 
  15. */  
  16.   
  17.   
  18. #include<stdio.h>  
  19. int father[2005];  
  20. int relation[2005];  
  21.   
  22. int find_father(int i)  
  23. {  
  24.     int t;  
  25.     if(father[i]==i)  
  26.         return i;  
  27.   
  28.     //计算相对于新的父节点(即根)的秩,relation[t]是老的父节点相对于新的父节点(即根)的秩,relation[i]是i元素相对于老的父节点的秩,  
  29.     //类似于物理里的相对运动,得到的r[i]就是相对于新的父节点(即根)的秩。而且这个递归调用不会超过两层  
  30.     t=father[i];      
  31.     father[i]=find_father(father[i]);  
  32.     relation[i]=(relation[i]+relation[t]+1)%2;   //注意递归中把这棵树relation中的的值都更新一遍,这句的顺序 不能 和上一句 调换位置  
  33.     // relation[a]的改变是伴随着father[a]的改变而更新的(有father改变就有relation改变),要是father改变了,而relation未改变,此时的relation就记录了一个错误的值,  
  34.     //father未改变(即使实际的father已不是现在的值,但只要father未改变,relation的值就是“正确”的,认识到这点很重要。)  
  35.     return father[i];  
  36. }  
  37.   
  38. void merge(int a,int b)  
  39. {  
  40.     int x,y;  
  41.     x=find_father(a);  
  42.     y=find_father(b);  
  43.     father[x]=y;  
  44.     relation[x]=(relation[b]-relation[a])%2;//relation[a]+relation[x]与relation[b]相对于新的父节点必须相差1个等级,因为他们不是gay  
  45. }                                            //x下边的节点不用改,因为查找的时候会自动更新  
  46.   
  47. int main()  
  48. {  
  49.     int T,m,n,i,j,a,b,flag;  
  50.     scanf("%d",&T);  
  51.     for(i=1;i<=T;++i)  
  52.     {  
  53.         flag=0;  
  54.         scanf("%d%d",&n,&m);  
  55.         for(j=1;j<=n;++j)       //初始化  
  56.         {  
  57.             father[j]=j;  
  58.             relation[j]=1;  
  59.         }  
  60.         for(j=1;j<=m;++j)  
  61.         {  
  62.             scanf("%d%d",&a,&b);  
  63.             if(find_father(a)==find_father(b))  
  64.             {  
  65.             //    if(relation[a]!=(relation[b]+1)%2)  
  66.                 if(relation[a]==relation[b])            //说明是同性  
  67.                     flag=1;  
  68.             }  
  69.             else  
  70.                 merge(a,b);  
  71.         }  
  72.         if(flag)  
  73.             printf("Scenario #%d:\nSuspicious bugs found!\n\n",i);  
  74.         else  
  75.             printf("Scenario #%d:\nNo suspicious bugs found!\n\n",i);  
  76.     }  
  77.     return 0;  
  78. }  

3、带权并查集经典题:poj1182食物链

我说不明白==参考:http://blog.csdn.net/c0de4fun/article/details/7318642

4、并查集水题 挨着就算相交,给定某个线段,询问这一团的有多少个

HDU 1558 Segment set 并查集

[cpp] view plain copy print?
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. using namespace std;  
  5. int pre[1010],sum[1010];  
  6. struct point{  
  7.     double x,y;  
  8. };  
  9. struct EDGE{  
  10.     point a,b;  
  11. } edge[1010];  
  12. int E;//边数  
  13. int Find(int x){  
  14.     return x==pre[x]? x:pre[x]=Find(pre[x]);  
  15. }  
  16. void Merge(int a,int b){  
  17.     int x=Find(a),y=Find(b);  
  18.     if(x!=y){  
  19.         pre[y]=x;  
  20.         sum[x]+=sum[y];  
  21.     }  
  22. }  
  23. double xmult(point a,point b,point c){//大于零代表a,b,c左转  
  24.     return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);  
  25. }  
  26. bool OnSegment(point a,point b,point c){        //a,b,c共线时有效  
  27.     return c.x>=min(a.x,b.x)&&c.x<=max(a.x,b.x)&&c.y>=min(a.y,b.y)&&c.y<=max(a.y,b.y);  
  28. }  
  29. bool Cross(point a,point b,point c,point d){//判断ab 与cd是否相交  
  30.     double d1,d2,d3,d4;  
  31.     d1=xmult(c,d,a);  
  32.     d2=xmult(c,d,b);  
  33.     d3=xmult(a,b,c);  
  34.     d4=xmult(a,b,d);  
  35.     if(d1*d2<0&&d3*d4<0)  return 1;  
  36.     else    if(d1==0&&OnSegment(c,d,a)) return 1;  
  37.     else    if(d2==0&&OnSegment(c,d,b)) return 1;  
  38.     else    if(d3==0&&OnSegment(a,b,c)) return 1;  
  39.     else    if(d4==0&&OnSegment(a,b,d)) return 1;  
  40.     return 0;  
  41. }  
  42. int main()  
  43. {  
  44.     int i,j,k,T,n;  
  45.     char s[10];  
  46.     scanf("%d",&T);  
  47.     while(T--)  
  48.     {  
  49.         scanf("%d",&n);E=0;  
  50.         for(i=1;i<=n;i++)    pre[i]=i,sum[i]=1;  
  51.         for(i=1;i<=n;i++)  
  52.         {  
  53.             scanf("%s",s);  
  54.             if(s[0]=='P'){  
  55.                 E++;  
  56.                 scanf("%lf%lf%lf%lf",&edge[E].a.x,&edge[E].a.y,&edge[E].b.x,&edge[E].b.y);  
  57.                 for(j=1;j<E;j++)  
  58.                     if(Find(E)!=Find(j)&&Cross(edge[E].a,edge[E].b,edge[j].a,edge[j].b))    Merge(E,j);  
  59.             }  
  60.             else    if(s[0]=='Q'){  
  61.                 scanf("%d",&k);  
  62.                 printf("%d\n",sum[Find(k)]);  
  63.             }  
  64.         }  
  65.         if(T)   printf("\n");  
  66.     }  
  67.     return 0;  
  68. }  
5、并查集浇灌农田:

HDU 1198 Farm Irrigation 并查集

给出每种小单元的上下左右联通情况,求最后整个农田分几块?把每种田地的四个边转化成数组形式,相邻的相连就用并查集

[cpp] view plain copy print?
  1. #include <iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. using namespace std;  
  5. bool type[15][4]={{1,0,0,1},{1,1,0,0},{0,0,1,1},{0,1,1,0},  
  6. {1,0,1,0},{0,1,0,1},{1,1,0,1},{1,0,1,1},{0,1,1,1},{1,1,1,0},{1,1,1,1}};  
  7. int f[300000],n,m;  
  8. char c;  
  9. int num[100][100];  
  10. void init(int n){  
  11.     for(int i=1;i<=n;i++) f[i]=i;  
  12. }  
  13. int find(int x)  
  14. {  
  15.     if(x==f[x]) return x;  
  16.     int tmp=f[x]; f[x]=find(f[x]);  
  17.     return f[x];  
  18. }  
  19. int main()  
  20. {  
  21.    //freopen("cin.txt","r",stdin);  
  22.     while(~scanf("%d%d",&m,&n))  
  23.     {  
  24.         if(m==-1&&n==-1) break;  
  25.         init(n*m);  
  26.         for(int i=1;i<=m;i++)  
  27.         {  
  28.             for(int j=1;j<=n;j++)  
  29.             {  
  30.               cin>>c;  
  31.               num[i][j]=c-65;  
  32.               //cout<<num[i][j]<<" ";  
  33.             }  
  34.            // cout<<endl;  
  35.         }  
  36.         int count=n*m;  
  37.         for(int i=1;i<=m;i++)  
  38.             for(int j=1;j<n;j++)  
  39.             {  
  40.                 if(type[num[i][j]][1]&&type[num[i][j+1]][3])  
  41.                 {  
  42.                     int fx=find(i*n-n+j),fy=find(i*n-n+j+1);  
  43.                     if(fx!=fy) {f[fx]=fy;count--;}  
  44.                 }  
  45.             }  
  46.         for(int i=1;i<=n;i++)  
  47.             for(int j=1;j<m;j++)  
  48.             {  
  49.                 if(type[num[j][i]][2]&&type[num[j+1][i]][0])//就是这里 这里这里~~  
  50.                 {  
  51.                     int fx=find(j*n-n+i),fy=find(j*n+i);  
  52.                     if(fx!=fy) {f[fx]=fy;count--;}  
  53.                 }  
  54.             }  
  55.   
  56.   
  57.         cout<<count<<endl;  
  58.     }  
  59.     return 0;  
  60. }  

6、

hdu1272小希的迷宫【并查集基础】

题意:问连接两条边是否有环,这个并查集的题用到了find函数的返回值,两个即将连接的点返回值如果相同,那么就说明要成环了!话说是不是好多图论题也可以这么搞呢?

[cpp] view plain copy print?
  1. #include <iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. using namespace std;  
  5. int a[100005],x,y,count,maxn,edge,point;  
  6. bool vis[100005],mark;  
  7. int find(int x)  
  8. {  
  9.     if(x!=a[x]) a[x]=find(a[x]);  
  10.     return a[x];  
  11. }  
  12. void addto(int x,int y)  
  13. {  
  14.     x=find(x),y=find(y);  
  15.     if(x==y) {  
  16.         mark=0;  
  17.         return ;  
  18.     }  
  19.     a[y]=a[x];  
  20.     return ;  
  21. }  
  22. void cal()  
  23. {  
  24.     for(int i=1;i<=maxn;i++)  
  25.     {  
  26.         if(vis[i])  
  27.         {  
  28.             if(a[i]==i) count++;  
  29.            // point++;  
  30.            if(count>1) mark=0;  
  31.         }  
  32.     }  
  33. }  
  34.   
  35. int main()  
  36. {  
  37.    // freopen("cin.txt","r",stdin);  
  38.     while(~scanf("%d%d",&x,&y))  
  39.     {  
  40.         if(x==-1&&y==-1) break;  
  41.         if(x==0&&y==0)  
  42.         {  
  43.             printf("Yes\n");  
  44.             continue;  
  45.         }  
  46.         memset(vis,0,sizeof(vis));  
  47.         for(int i=1;i<=100000;i++) a[i]=i;  
  48.         mark=1;  
  49.         count=0;  
  50.         maxn=0;  
  51.        // point=0;  
  52.         addto(x,y);  
  53.         vis[x]=1;  
  54.         vis[y]=1;  
  55.         if(maxn<x) maxn=x;  
  56.         if(maxn<y) maxn=y;  
  57.          while(~scanf("%d%d",&x,&y))  
  58.         {  
  59.             if(x==0&&y==0) break;///  
  60.             addto(x,y);  
  61.             vis[x]=1;  
  62.             vis[y]=1;  
  63.             if(maxn<x) maxn=x;  
  64.             if(maxn<y) maxn=y;  
  65.         }  
  66.         cal();  
  67.         if(mark==1) printf("Yes\n");  
  68.         else printf("No\n");  
  69.     }  
  70.     return 0;  
  71. }  

7、

hdu3461Code Lock【并查集+快速幂】

这个题的题意晦涩难懂,总的做法就是每次将两个集合合并(原来两个点就在一个集合内的不算)合并一次,一个最开始是等于长度的变量--,最后对这个变量^26取模(快速幂)

[cpp] view plain copy print?
  1. #include <iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. using namespace std;  
  5. #define maxn 10000005  
  6. #define mod 1000000007  
  7. int f[maxn],n,m,l,r,count;  
  8. bool vis[maxn];  
  9. int find(int x)  
  10. {  
  11.     if(x!=f[x]) f[x]=find(f[x]);  
  12.     return f[x];  
  13. }  
  14. void addto(int x,int y)  
  15. {  
  16.     x=find(x),y=find(y);  
  17.     if(x==y) return ;  
  18.     f[x]=y;  
  19.     count++;  
  20. }  
  21. long long multi(int x)  
  22. {  
  23.     long long ans=1,tmp=26;  
  24.     while(x)  
  25.     {  
  26.         if(x&1) ans=(ans*tmp)%mod;  
  27.         tmp=(tmp*tmp)%mod;  
  28.         x>>=1;  
  29.     }  
  30.     return ans;  
  31. }  
  32. int main()  
  33. {  
  34.    // freopen("cin.txt","r",stdin);  
  35.     while(~scanf("%d%d",&n,&m))  
  36.     {  
  37.         for(int i=1;i<=n+1;i++) f[i]=i;  
  38.         count=0;  
  39.         while(m--)  
  40.         {  
  41.             scanf("%d%d",&l,&r);  
  42.             addto(l,r+1);  
  43.             vis[l]=1,vis[r+1]=1;  
  44.         }  
  45.         /*for(int i=1;i<=n+1;i++) 
  46.         { 
  47.             if(vis[i]&&f[i]==i) count++; 
  48.         }*/  
  49.         printf("%lld\n",multi(n-count)%mod);  
  50.     }  
  51.     return 0;  
  52. }  

8、

poj3177Redundant Paths【构造双连通分量:并查集缩点 模板】

第六个题刚刚说完图论,这就来了==本题意在求出加入多少边可以构成双连通分量,而构造双连通分量的加边数=(原图的叶节点数+1)/2,用并查集缩点,枚举每个桥,[团]++,=1说明是叶子节点

[cpp] view plain copy print?
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4. #include <vector>  
  5. #include <stack>  
  6.   
  7. using namespace std;  
  8.   
  9. const int N=5006;  
  10. vector<int>G[N];  
  11. struct bridge  
  12. {  
  13.     int u,v;  
  14. }bg[2*N];  
  15. int vis[N],low[N],dfn[N],Time;  
  16. int fa[N],deg[N];  
  17. int n,m,cnt;  
  18. void init()  
  19. {  
  20.     for(int i=0;i<n;i++) G[i].clear();  
  21.     memset(dfn,0,sizeof(dfn));  
  22.     memset(low,0,sizeof(low));  
  23.     memset(vis,0,sizeof(vis));  
  24.     memset(deg,0,sizeof(deg));  
  25.     for(int i=1;i<=n;i++) fa[i]=i;  
  26.     cnt=Time=0;  
  27. }  
  28. int findset(int x)  
  29. {  
  30.     if(x!=fa[x])  
  31.         fa[x]=findset(fa[x]);  
  32.     return fa[x];  
  33. }  
  34. void Tarjan(int u,int father)  
  35. {  
  36.     low[u] = dfn[u] = ++Time;  
  37.     vis[u] = 1;  
  38.     for(int i=0;i<G[u].size();i++)  
  39.     {  
  40.         int v = G[u][i];  
  41.         if(v == father)  
  42.             continue;  
  43.         if(!vis[v])  
  44.         {  
  45.             Tarjan(v,u);  
  46.             low[u] = min(low[u],low[v]);  
  47.             if(low[v] > dfn[u])        //u->v为桥  
  48.                 bg[cnt].u = u,bg[cnt++].v = v;  
  49.             else   //否则,u,v同属一个连通分量,合并  
  50.             {  
  51.                 int fx = findset(u);  
  52.                 int fy = findset(v);  
  53.                 if(fx != fy)  
  54.                     fa[fx] = fy;  
  55.             }  
  56.         }  
  57.         else  
  58.             low[u] = min(low[u],dfn[v]);  
  59.     }  
  60. }  
  61.   
  62. int main()  
  63. {  
  64.    // freopen("cin.txt","r",stdin);  
  65.     while(~scanf("%d%d", &n, &m))  
  66.     {  
  67.         init();  
  68.         for(int i=0;i<m;i++)  
  69.         {  
  70.             int u,v;  
  71.             scanf("%d%d",&u,&v);  
  72.             G[u].push_back(v);  
  73.             G[v].push_back(u);  
  74.         }  
  75.         Tarjan(1,-1);  
  76.         for(int i=0;i<cnt;i++)  
  77.         {  
  78.             int fx=findset(bg[i].u);  
  79.             int fy=findset(bg[i].v);  
  80.             deg[fx]++;  
  81.             deg[fy]++;  
  82.         }  
  83.         int leaf=0;  
  84.         for(int i=1;i<=n;i++)  
  85.             if(deg[i]==1)  
  86.                 leaf++;  
  87.         printf("%d\n",(leaf+1)/2);  
  88.     }  
  89.     return 0;  
  90.   
  91. }  
9、

hdu5606 bestcoder#68 div2tree【并查集】

有一个树(n个点, n-1条边的联通图),点标号从1~n,树的边权是0或1.求离每个点最近的点个数(包括自己)。
距离是0的。两个团连在一起,维护以某点为根节点的点的个数

[cpp] view plain copy print?
  1. #include <iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. using namespace std;  
  5. int n,t,a[100005],num[100005],cnt;  
  6. int fnd(int x)  
  7. {  
  8.     if(x!=a[x]) a[x]=fnd(a[x]);  
  9.     return a[x];  
  10. }  
  11. void addto(int x,int y)  
  12. {  
  13.     x=fnd(x),y=fnd(y);  
  14.     if(x==y) {  
  15.         return ;  
  16.     }  
  17.     a[y]=a[x];  
  18.     num[x]+=num[y];  
  19.     return ;  
  20. }  
  21. int main()  
  22. {  
  23.   //  freopen("cin.txt","r",stdin);  
  24.     scanf("%d",&t);  
  25.     while(t--)  
  26.     {  
  27.         scanf("%d",&n);  
  28.         cnt=0;  
  29.         for(int i=1;i<=n;i++) {num[i]=1;a[i]=i;}  
  30.         for(int i=1;i<n;i++)  
  31.         {  
  32.             int u,v,q;  
  33.             scanf("%d%d%d",&u,&v,&q);  
  34.             if(q==0)  addto(u,v);  
  35.         }  
  36.         //cout<<"44544"<<endl;  
  37.         cnt=0;  
  38.         for(int i=1;i<=n;i++)  
  39.         {  
  40.             if(a[i]==i&&(num[i]&1)) cnt^=num[i];  
  41.         }  
  42.         printf("%d\n",cnt);  
  43.     }  
  44.     return 0;  
  45. }  

10、

hdu3018Ant Trip【欧拉道路数量 并查集】

题意:给出一个图,问几笔画才能经过所有边 连通图有一个性质:其需要画的笔数=度数为奇数的点数除以2,那么由于给出的图并没有说明是否是连通图,我们需要用并查集来维护连通图,并且忽略单点的“子图” 所以说就和第8题一样了

[cpp] view plain copy print?
  1. #include <iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. #include<vector>  
  5. using namespace std;  
  6. int a[100005],deg[100005],odd[100005];  
  7. bool used[100005];  
  8. int fnd(int x)  
  9. {  
  10.     if(x!=a[x]) a[x]=fnd(a[x]);  
  11.     return a[x];  
  12. }  
  13. void addto(int x,int y)  
  14. {  
  15.     x=fnd(x),y=fnd(y);  
  16.     if(x==y) {  
  17.         return ;  
  18.     }  
  19.     a[y]=a[x];  
  20.     return ;  
  21. }  
  22. int main()  
  23. {  
  24.    // freopen("cin.txt","r",stdin);  
  25.     int n,m,x,y,cnt;  
  26.     while(~scanf("%d%d",&n,&m))  
  27.     {  
  28.         for(int i=1;i<=n;i++) {a[i]=i;deg[i]=0;used[i]=false;odd[i]=0;}  
  29.         for(int i=0;i<m;i++)  
  30.         {  
  31.             scanf("%d%d",&x,&y);  
  32.             deg[x]++;deg[y]++;  
  33.             x=fnd(x);y=fnd(y);  
  34.             if(x!=y)addto(x,y);  
  35.         }  
  36.         vector<int>v;  
  37.         for(int i=1;i<=n;i++)  
  38.         {  
  39.             int f=fnd(i);  
  40.             if(!used[f])  
  41.             {  
  42.                 v.push_back(f);  
  43.                 used[f]=1;//!!!  
  44.             }  
  45.             if(deg[i]&1)  
  46.             odd[f]++;  
  47.         }  
  48.         cnt=0;  
  49.         for(int i=0;i<v.size();i++)  
  50.         {  
  51.             int k=v[i];  
  52.             if(deg[k]==0) continue;  
  53.             if(odd[k]==0) cnt++;  
  54.             else cnt+=odd[k]/2;  
  55.         }  
  56.         printf("%d\n",cnt);  
  57.     }  
  58.     return 0;  
  59. }  

原创粉丝点击