描述:并查集是一种对集合进行合并、查询等一系列操作。时间复杂度为O(a(n)) 比O(logn)还快。
代码:
1 int fa[sz];//集合数组 2 void init()//预处理 3 { 4 for(int i=1;i<=n;i++) 5 { 6 fa[i]=i;//初始时每个点都是一个独立的集合 7 rank[i]=0;//按秩合并 初始时每一个集合形成的树的高度为0; 8 } 9 } 10 int find(int x)//查询操作 返回x所在集合的代表元素 11 { 12 return fa[x]==x?x:fa[x]=find(fa[x]); 13 //如果x就是它所在集合的代表元素 就返回x 14 //反之 让它到代表元素上的点的父节点都变成代表元素 并返回修改后父节点的值 即代表元素 15 //路径压缩 使并查集更加高效 16 } 17 void unite(int u,int v)//合并操作 18 { 19 u=find(u),v=find(v);//对u和v的集合进行合并操作 因此将它们变成其所在集合代表元素 20 if(u==v) return ;//如果u和v在同一集合内 则不必进行合并操作 21 //按秩合并 防止树退化成链 22 if(rank[u]<rank[v])//如果u和v所在子树的rank不同 23 { 24 fa[u]=v;//就将rank小的合并到rank大的上 25 } 26 else 27 { 28 fa[v]=u; 29 if(rank[u]==rank[v]) 30 rank[u]++; 31 } 32 } 33 bool same(int u,int v)//判断两个元素是否属于同一集合 34 { 35 u=find(u),v=find(v); 36 return u==v; 37 }
拓展及应用:
①拓展域并查集
例题:
思路:每个节点维护两个域--朋友域fa[x]和敌人域fa[x+n],若u与v是敌人,u的敌人域等于v的朋友域,遂合并。
若u与v是朋友,就将两者的朋友域进行合并。最后看每个节点属于多少个不同的集合即为答案。
AC Code:
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 const int sz=1000+100; 5 int fa[sz<<1]; 6 int find(int x) 7 { 8 return fa[x]==x?x:fa[x]=find(fa[x]); 9 } 10 bool vis[sz<<1]; 11 int main() 12 { 13 int n,m; 14 scanf("%d%d",&n,&m); 15 char c;int u,v; 16 for(int i=1;i<=(n<<1);i++) 17 { 18 fa[i]=i; 19 } 20 for(int i=1;i<=m;i++) 21 { 22 cin>>c>>u>>v; 23 if(c==‘F‘) 24 { 25 fa[find(u)]=find(v); 26 } 27 if(c==‘E‘) 28 { 29 fa[find(v)]=find(u+n); 30 fa[find(v+n)]=find(u); 31 } 32 } 33 for(int i=1;i<=n;i++) 34 vis[find(i)]=true; 35 int ans=0; 36 for(int i=1;i<=(n<<1);i++) 37 if(vis[i]) ans++; 38 printf("%d",ans); 39 return 0; 40 }
思路:与上面的题目类似,对每个动物维护三个域:同类域fa[x],吃域fa[x+n],被吃域fa[x+n*2]。
若x与y是同类,就将它们的三个域分别合并;若x被y吃,则x的同类域等于y的吃域,x的吃域等于y的被吃域,x的被吃域等于y的同类域。
AC Code:
#include<cstdio> using namespace std; const int sz=300000+500; int fa[sz]; int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } int main() { int n,k; scanf("%d%d",&n,&k); for(int i=1;i<=3*k;i++) fa[i]=i; int num,x,y,ans=0; for(int i=1;i<=k;i++) { scanf("%d%d%d",&num,&x,&y); if(x>n||y>n)//若x或y超过了范围,则为假话 { ans++;continue; } int x1=find(x),x2=find(x+n),x3=find(x+2*n);//x1=x的同类域 x2=x的吃域 x3=x的被吃域 int y1=find(y),y2=find(y+n),y3=find(y+2*n); if(num==1)//若x与y是同类 { if(x1==y2||x2==y1) ans++;//若存在吃与被吃关系 则为假话 else { fa[x1]=y1;fa[x2]=y2;fa[x3]=y3;//若不矛盾 则为真话 合并集合 } } if(num==2)//若x吃y { if(x1==y1||x1==y2) ans++;//若x与y是同类 或x被y吃 则为假话 else { fa[x1]=y3;fa[x2]=y1;fa[x3]=y2;//反之,x的同类域等于y的吃域,x的吃域等于y的被吃域,x的被吃域等于y的同类域 } } } printf("%d",ans); return 0; }
②带权并查集
学习ing 以后再填。。。
①最小生成树 MST
并查集是Kruskal的灵魂。
例题:
思路:Kruskal大家应该都会吧....
AC Code:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int sz=50000+50; 6 int n,m; 7 struct node{ 8 int x,y,w; 9 }e[sz*2]; 10 bool cmp(node a,node b) 11 { 12 return a.w<b.w; 13 } 14 int fa[sz]; 15 int find(int x) 16 { 17 return fa[x]==x?x:fa[x]=find(fa[x]); 18 } 19 int ans; 20 void kruskal() 21 { 22 ans=-999; 23 for(int i=1;i<=m;i++) 24 { 25 int x=e[i].x,y=e[i].y; 26 if(find(x)!=find(y)) 27 { 28 fa[find(x)]=find(y); 29 ans=max(ans,e[i].w); 30 } 31 } 32 } 33 int main() 34 { 35 scanf("%d%d",&n,&m); 36 for(int i=1;i<=m;i++) 37 { 38 int u,v,w; 39 scanf("%d%d%d",&u,&v,&w); 40 e[i].x=u,e[i].y=v,e[i].w=w; 41 } 42 sort(e+1,e+m+1,cmp); 43 for(int i=1;i<=n;i++) 44 fa[i]=i; 45 kruskal(); 46 printf("%d %d",n-1,ans); 47 return 0; 48 }
②求最小环
.
例题:P2661 信息传递
思路:这道题可以DFS过,也可以用并查集做
AC Code:
③求满足要求的集合个数
看有多少x的fa[x]=x。
例题:P3420 [POI2005]SKA-Piggy Banks
思路:将钥匙相同的存钱罐连成一个大存钱罐,最后看大存钱罐的数量。
AC Code:
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int sz=1000000+100; 5 int fa[sz]; 6 int find(int x) 7 { 8 return fa[x]==x?x:fa[x]=find(fa[x]); 9 } 10 int main() 11 { 12 int n; 13 scanf("%d",&n); 14 for(int i=1;i<=n;i++) 15 fa[i]=i; 16 for(int i=1;i<=n;i++) 17 { 18 int k; 19 scanf("%d",&k); 20 int u=find(i),v=find(k); 21 fa[u]=v; 22 } 23 int cnt=0; 24 for(int i=1;i<=n;i++) 25 if(fa[i]==i) 26 cnt++; 27 printf("%d",cnt); 28 return 0; 29 }
④求LCA
?
End.
时间: 2024-11-05 20:26:42