zoukankan      html  css  js  c++  java
  • 并查集:从入门到入土

    并查集:从入门到入土

    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

  • 相关阅读:
    Yii2——MYSQL操作
    Git之路——Git的使用
    python之路——爬虫实例
    PHP之路——验证码实现
    PHP之路——PHPExcel使用
    PHP之路——Apache启动失败查看日志
    pycharm——常用快捷键操作
    python_常用断言assert
    python通过一句话判断闰年的代码
    通过Python实现mysql查询数据库实例
  • 原文地址:https://www.cnblogs.com/ck666/p/7464987.html
Copyright © 2011-2022 走看看