博客园 首页 私信博主 显示目录 隐藏目录 管理

并查集:从入门到入土

并查集:从入门到入土

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;
}
P1196 银河英雄传说

就这样过了浑浑噩噩的一天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;
}
luogu 2839

很简单对不对

那么我们看下一题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;
}
luogu 1525

 


银河英雄传说:

这中二的题面……还是一道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;
}
luogu 1196

 

一个下午泡在并查集上……

 

by:wypx


 

s:芙兰一块吃西瓜啊

w:芙兰会先把你吃了的x

posted @ 2017-09-01 20:29  ck666  阅读(973)  评论(0编辑  收藏  举报