并查集题目从入门到入土
Posted ck666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集题目从入门到入土相关的知识,希望对你有一定的参考价值。
并查集:从入门到入土
2017-09-01
并查集一个神奇的算法
今天我们的s同学想学习一下并查集,就去找了几个水题刷一下...
入门题:P2839 畅通工程
就是求联通块的数量,-1就是答案。
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; int read(){ int f=1,an=0; char ch=getchar(); while(!(\'0\'<=ch&&ch<=\'9\')){if(ch==\'-\')f=-f;ch=getchar();} while(\'0\'<=ch&&ch<=\'9\'){an=an*10+(ch-\'0\');ch=getchar();} return f*an; } int f[1000+99];bool c[1000+99]; int n,m,ans; int from,to; void add(int x,int y){ int xx=x,yy=y; while(xx!=f[xx])xx=f[xx]; while(yy!=f[yy])yy=f[yy]; if(xx!=yy)f[xx]=yy; } int find(int i){ int k=i; while(f[k]!=k){c[k]=0;k=f[k];} } int main(){ n=read();m=read(); for(int i=1;i<=n;i++){f[i]=i;c[i]=1;} for(int i=1;i<=m;i++){ from=read(); to=read(); add(from,to); } for(int i=1;i<=n;i++){ find(i);} for(int i=1;i<=n;i++)if(c[i])ans++; cout<<ans-1; return 0; }
是不是很简单
然后提高题
P1525 关押罪犯
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> using namespace std; const int maxn=100000+99999; int read(){ int an=0,f=1; char ch=getchar(); while(!(\'0\'<=ch&&ch<=\'9\')){if(ch==\'-\');ch=getchar();} while(\'0\'<=ch&&ch<=\'9\'){an=an*10+(ch-\'0\');ch=getchar();} return f*an; } int n,m; int b[maxn]; struct saber{ int a,b,wi; }e[maxn]; int f[maxn*2]; int ans,ta1,ta2; bool sa(int x,int y){ return e[x].wi>e[y].wi;} int found(int x){ if(f[x]!=x)f[x]=found(f[x]); return f[x]; } int main(){ n=read();m=read(); for(int i=1;i<=m;i++){ e[i].a=read(); e[i].b=read(); e[i].wi=read(); } for(int i=1;i<=m;i++)b[i]=i; sort(b+1,b+1+m,sa); for(int i=1;i<=2*n;i++)f[i]=i; for(int i=1;i<=m;i++){ int k1=found(e[b[i]].a); int k2=found(e[b[i]].b); if(k1==k2){printf("%d",e[b[i]].wi);return 0;} f[k2]=found(n+e[b[i]].a); f[k1]=found(n+e[b[i]].b); } cout<<"0"; return 0; }
最后水一下这个中二的题目
P1196 银河英雄传说
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> using namespace std; const int maxn=30000+99; int read(){ int f=1,an=0; char ch=getchar(); while(!(\'0\'<=ch&&ch<=\'9\')){if(ch==\'-\')f=-f;ch=getchar();} while(\'0\'<=ch&&ch<=\'9\'){an=an*10+(ch-\'0\');ch=getchar();} return an*f; } int T; int f[maxn],sum[maxn],s[maxn]; int found(int x){ if(f[x]!=x){ int k=found(f[x]); s[x]+=s[f[x]]; f[x]=k; } return f[x]; } char c; int a,b; int main(){ T=read();for(int i=1;i<=30000;i++)f[i]=i,sum[i]=1; while(T){T--; cin>>c;a=read();b=read(); int k1,k2; k1=found(a);k2=found(b); if(c==\'M\'){ f[k1]=k2; s[k1]=sum[k2]; sum[k2]+=sum[k1]; } else{ if(k1==k2)cout<<abs(s[a]-s[b])-1<<endl; else cout<<"-1"<<endl;} } return 0; }
就这样过了浑浑噩噩的一天qwq。...
by:s_a_b_e_r
↑丢的一手好代码,但是题解呢:)
做题之前先安利一篇文章
讲并查集讲的很神奇x
于是下面丢题解
畅通工程:
并查集入门级题(废话x
先跑一遍并查集板子
求出连通块个数k
那么只需要再加上k-1条边就能全连通了
#include<iostream> #include<cstdio> using namespace std; const int N=1000; int n,m,fa[N]; bool f[N]; int found(int x) { if(fa[x]==x)return x; return fa[x]=found(fa[x]); } void add(int x,int y) { int p=found(x),q=found(y); if(p!=q)fa[p]=q; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)fa[i]=i; for(int i=1;i<=m;++i) { int x,y; scanf("%d%d",&x,&y); add(x,y); } for(int i=1;i<=n;++i)f[found(i)]=true; int ans=0; for(int i=1;i<=n;++i)if(f[i])++ans; cout<<ans-1<<endl; return 0; }
很简单对不对
那么我们看下一题x
关押罪犯:
我们认识的并查集君一般都是用来维护两个点在同一连通块里
然而这题要维护两个点不在同一连通块里
于是就有一种很神奇的做法
把每一个点拆成一个实点A和一个虚点A\'
把维护“A和B不在同一连通块”转化成“A\'和B在同一连通块”
这样就好解决了
每次贪心选剩余点对中怨气值最大的点对
如果两个点不在同一连通块就加进不同连通块
如果两个点在同一连通块的话就直接输出这个怨气值就可以了
丢一波代码
#include<iostream> #include<cstdio> #include<algorithm> const int N=20009,M=100009; using namespace std; struct pai{ int a,b,c; }p[M<<1]; bool cmp(pai x,pai y){return x.c>y.c;} int n,m,fa[N<<1]; int found(int x) { if(x==fa[x])return x; return fa[x]=found(fa[x]); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n*2;++i)fa[i]=i; for(int i=1;i<=m;++i) scanf("%d%d%d",&p[i].a,&p[i].b,&p[i].c); sort(p+1,p+1+m,cmp); for(int i=1;i<=m;++i) { int x=found(p[i].a),y=found(p[i].b); if(x==y){cout<<p[i].c;return 0;} fa[y]=found(p[i].a+n),fa[x]=found(p[i].b+n); } cout<<0; return 0; }
银河英雄传说:
这中二的题面……还是一道NOI……
乍一看好像不能路径压缩……那并查集就完全没用了啊?
然而在并查集之上还有一种东西叫做加权并查集
用一个s数组来保存一个点到并查集根结点的距离
在路径压缩的时候顺便更新一下距离
最后输出的时候就输出两个点到根结点的距离之差-1就可以了
具体实现看代码吧w
#include<iostream> #include<cstdio> #include<cmath> using namespace std; const int N=30005; int t,fa[N],s[N],sum[N]; int read() { int f=1,an=0; char ch=getchar(); while(!(\'0\'<=ch&&ch<=\'9\')){if(ch==\'-\')f=-1;ch=getchar();} while((\'0\'<=ch&&ch<=\'9\')){an=an*10+(ch-\'0\');ch=getchar();} return an*f; } int found(int x) { if(x==fa[x])return x; int f=found(fa[x]); s[x]+=s[fa[x]]; fa[x]=f; return fa[x]; } int main() { t=read(); for(int i=1;i<=N;++i){fa[i]=i;sum[i]=1;} while(t--) { char c; cin>>c; int a=read(),b=read(); int f1=found(a),f2=found(b); if(c==\'M\') { fa[f1]=f2; s[f1]=sum[f2]; sum[f2]+=sum[f1]; } else { if(f1==f2)cout<<abs(s[a]-s[b])-1<<endl; else cout<<-1<<endl; } } return 0; }
一个下午泡在并查集上……
by:wypx
s:芙兰一块吃西瓜啊
w:芙兰会先把你吃了的x
以上是关于并查集题目从入门到入土的主要内容,如果未能解决你的问题,请参考以下文章