洛谷专题-并查集
P1196 [NOI2002]银河英雄传说(带权并查集)
题意:
有n艘舰依次排序,每次将i及其身后的舰艇合并至j及其所有舰艇之后,每次询问i到j舰艇之间的距离,如果不在一列输出-1
思路:
单纯的合并与查询是否在一列操作比较简单,难的在于查询距离
首先我们需要三个数组fa[i],sum[i],dis[i]分别为i的父亲,i列所有的舰艇数,与其到其父亲的距离
可能有人会想i到其父亲的距离不都是1嘛,其实在路径压缩过程中,父亲会与实际的情况不符,虽然直接相连但是可能距离并不为1
现在考虑合并(i,j)操作,每次合并操作只要对第一艘舰艇进行修改就好了,分别修改
dis数组的修改直接等于sum[j],之后将sum[j]+=sum[i],并将sum[i]=0
在每次查询时都会进行路径压缩,因此dis[k](k为排在i之后的舰艇)虽然在合并时没有修改,但是会在路径压缩(查询父亲)时修改成到该列第一艘舰艇的距离
之后在利用前缀后的思想,(dis[i]-dis[j])-1即为连个舰艇之间的舰艇数了
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> using namespace std; const int maxn=3e4+10; int fa[maxn],sum[maxn],dis[maxn]; int find(int x) { if(fa[x]==x) return x; int f1=fa[x],f2=find(fa[x]); dis[x]+=dis[f1]; fa[x]=f2; return f2; } void uni(int x,int y) { int f1=find(x),f2=find(y); fa[f2]=f1; dis[f2]=sum[f1]; sum[f1]+=sum[f2]; sum[f2]=0; } int main() { int t,i,j; char op; scanf("%d",&t); for(int i=1;i<=maxn;i++){ fa[i]=i; sum[i]=1; } while(t--){ cin>>op>>i>>j; if(op=='M') uni(j,i); else{ if(find(i)!=find(j)) cout<<-1<<endl; else{ cout<<abs(dis[j]-dis[i])-1<<endl; } } } return 0; }
P2024 [NOI2001]食物链(种类并查集)
题意:
现在有三种生物,ABC,A吃B,B吃C,C吃A,现在依次告诉你一些关系,请说出这些关系中假话的个数(假话的定义为与之前的话矛盾或不符合事实例如A吃A)
思路:
对于一对关系,比较难处理的是虽然你知道X吃Y,但是你不知道X跟Y究竟属于什么物种
那么我们可以建立3*N大小的并查集,分为A,B,C三个部分,代表着A中的生物吃B中的生物等等类推
对于一个关系比如X跟Y一类,我们在三个集合中分别将二者相连
对于关系X吃Y,我们就将A中的X与B中的Y相连,还有两个集合中操作类似
对于每个关系,我们就可以通过判断在一个集合内是否相连,或者在另一个集合内相连来判断正误了
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> using namespace std; const int maxn=2e5+10; int fa[maxn]; int find(int x){return fa[x]==x?x:(fa[x]=find(fa[x]));} int main() { int n,k,ans=0,op,x,y; scanf("%d%d",&n,&k); for(int i=1;i<=3*n;i++) fa[i]=i; for(int i=1;i<=k;i++){ scanf("%d%d%d",&op,&x,&y); if(x>n||y>n){ ans++; continue; } if(op==1){ if(find(x)==find(y+n)||find(y)==find(x+n)) ans++; else{ fa[find(x)]=find(y); fa[find(x+n)]=find(y+n); fa[find(x+2*n)]=find(y+2*n); } } else{ if(x==y){ans++;continue;} if(find(x)==find(y)||find(x)==find(y+2*n)) ans++; else{ fa[find(x)]=find(y+n); fa[find(x+n)]=find(y+2*n); fa[find(x+2*n)]=find(y); } } } cout<<ans<<endl; return 0; }
P1197 [JSOI2008]星球大战(逆向思维,并查集)
题意:
给你一个无向图,每次从图中删去一个点,询问每次删点过后图中连通块的数量
思路:
本题可以离线,因此我们采用离线的逆向做法
怎么个逆向呢?我们假设一开始只有所有删点操作之后的点,并算出连通块个数
之后每次向图中加入被删除的点,并统计连通块个数
如果重新对所有点跑一遍的话时间复杂度上一定会炸,对于新加入的点,我们先对当前连通块个数加1,如果遍历该点连接的所有点,如果能够合并,那么就将连通块个数减1
最后把答案倒序输出就好了
#include<cstdio> #include<iostream> #include<algorithm> #include<vector> #include<stack> #include<cstring> using namespace std; const int maxn=4e5+1000; int flag[maxn],fa[maxn]; vector<int>a[maxn]; stack<int> s,q; int find(int x){return fa[x]==x?x:(fa[x]=find(fa[x]));} int main() { int n,m,k,u,v; memset(flag,1,sizeof(flag)); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ scanf("%d%d",&u,&v); a[u].push_back(v); a[v].push_back(u); } scanf("%d",&k); for(int i=1;i<=k;i++){ scanf("%d",&u); s.push(u); flag[u]=0; } int cnt=n-k; for(int i=0;i<n;i++) fa[i]=i; for(int i=0;i<n;i++){ if(!flag[i]) continue; else{ for(int j=0;j<a[i].size();j++){ if(flag[a[i][j]]){ int f1=find(a[i][j]),f2=find(i); if(f1!=f2) fa[f1]=find(fa[f2]),cnt--; } } } } q.push(cnt); for(int i=1;i<=k;i++){ cnt++; int x=s.top(); s.pop(); for(int j=0;j<a[x].size();j++){ if(flag[a[x][j]]){ int f1=find(a[x][j]),f2=find(x); if(f1!=f2) fa[f1]=find(fa[f2]),cnt--; } } flag[x]=1; q.push(cnt); } while(!q.empty()){ cout<<q.top()<<endl; q.pop(); } return 0; }
P1111 修复公路(并查集)
题意:
给你n个点,m条无向边,每条边建好都有一个时间,问什么时候各个点能互相可达
思路:
将每条边按时间排序,每次加入一条边,看边连接的两点是否在一个连通块内,不在的话合并连通块,看是否总连通块个数为1即可
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> using namespace std; const int maxn1=1e5+10; const int maxn2=1e3+10; int fa[maxn2],siz[maxn2],n,m; struct node{ int u,v,t; }edge[maxn1]; int cmp(node a,node b){return a.t<b.t;} int find(int x){return fa[x]==x?x:(fa[x]=find(fa[x]));} int main() { scanf("%d%d",&n,&m); for(int i=0;i<m;i++) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].t); sort(edge,edge+m,cmp); for(int i=1;i<=n;i++) fa[i]=i; memset(siz,0,sizeof(siz)); int cnt=n; for(int i=0;i<m;i++){ int u=edge[i].u,v=edge[i].v,t=edge[i].t; int f1=find(u),f2=find(v); if(f1!=f2) fa[f1]=f2,cnt--; if(cnt==1){ cout<<t<<endl; return 0; } } cout<<-1<<endl; return 0; }