zoukankan      html  css  js  c++  java
  • 并查集及扩展域与边带权

    并查集是个很实用的数据结构,主要功能可以动态维护若干个不重叠的集合,并支持合并与查询,当然这些都只是概念,个人理解并查集能够维护具有传递性的关系,还可有维护节点之间的连续性,克鲁斯卡尔就是这样做到的.普通的并查集就是如此,今天介绍并查集的扩展,看题:

    题目一看便知道要用到并查集,判断在不在一列可以普通的并查集就能实现,可是如何实现了解两艘战舰之间的距离呢?

    我刚开始也是直接暴力枚举,加上一个son数组和dis数组,dis表示点到点所在树的根的距离,每次合并两个树时,暴力修改。

    最后辛辛苦苦把代码写完,就得了50,以下是暴力代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=31000;
    int f[maxn],t,son[maxn],dis[maxn];
    char ch;
    inline int read()
    {
        int x=0,ff=1;
        char ch=getchar();
        while(!isdigit(ch))
        {
            if(ch=='-') ff=-1;
            ch=getchar();
        }
        while(isdigit(ch))
        {
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*ff;
    }
    inline void put(int x)
    {
        if(x<0) putchar('-'),x=-x;
        if(x>9) put(x/10);
        putchar(x%10+'0');
    }
    inline int getf(int x)
    {
        if(f[x]==x) return x;
        else return (getf(f[x]));
    }
    inline int gets(int x)
    {
        if(son[x]==x) return x;
        else return (gets(son[x]));
    }
    inline void work1(int tou,int wei)
    {
        f[tou]=wei;son[wei]=tou;
        int i=tou;
        while(1)
        {
            if(i==son[i]) {dis[i]=dis[f[i]]+1;break;}
            dis[i]=dis[f[i]]+1;
            i=son[i];
        }
    }
    int main()
    {
        //freopen("1.in","r",stdin);
        t=read();
        for(int i=1;i<=30000;i++) f[i]=i,son[i]=i;
        for(int i=1;i<=t;i++)
        {
            cin>>ch;
            int x=read(),y=read();
            int t1=getf(x),t2=gets(x);
            int t3=getf(y),t4=gets(y);
            if(ch=='M') work1(t1,t4);
            if(ch=='C') 
            {
                if(t1==t3) cout<<abs(dis[x]-dis[y])-1<<endl;
                else       cout<<-1<<endl;
            }
        }
        return 0;
    }
    View Code

    实在不行就去看了算法进阶,才知到不需维护各个点到根的距离,那样复杂度太高,可以维护每个点与它f[x]的权值(也可理解为从根开始的编号,根不算在内),为什么呢,因为这样你在找父亲与路径压缩时就可以顺便把点x到根的距离解决,因为每次路径压缩都是将路上各点单独拿出来与根相连,距离也就可以解决。找父亲代码:

    inline int getf(int x)
    {
        if(f[x]==x) return x; //找到根,返回。 
        int root=getf(f[x]); //找根。 
        dis[x]+=dis[f[x]];  //因为此时经过回溯,dis[f[x]]的距离已经是 f[x]到根的距离了,所以 
        return f[x]=root;  //dis[x]只需将原本与父亲相连的权值加上父亲的距离即是与跟的距离。 
    }                     //最后加上路径压缩. 

    解决了找父亲,接下来就是合并了,两个子树合并,需要求出x的到y的距离(x,y为两个子树的根,且x要到y上),显然,dis[s]便是y的size(),所以还需维护以个size()数组表示各个集合的点数量,

    代码:

    inline void merge(int x,int y)
    {
        f[x]=y;        
        dis[x]=size[y];
        size[y]+=size[x];
    }

    把这些解决了,题目也就没那么难了,总代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=31000;
    int f[maxn],t,dis[maxn],size[maxn];
    char ch;
    inline int read()
    {
        int x=0,ff=1;
        char ch=getchar();
        while(!isdigit(ch))
        {
            if(ch=='-') ff=-1;
            ch=getchar();
        }
        while(isdigit(ch))
        {
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*ff;
    }
    inline void put(int x)
    {
        if(x<0) putchar('-'),x=-x;
        if(x>9) put(x/10);
        putchar(x%10+'0');
    }
    inline int getf(int x)
    {
        if(f[x]==x) return x; //找到根,返回。 
        int root=getf(f[x]); //找根。 
        dis[x]+=dis[f[x]];  //因为此时经过回溯,dis[f[x]]的距离已经是 f[x]到根的距离了,所以 
        return f[x]=root;  //dis[x]只需将原本与父亲相连的权值加上父亲的距离即是与跟的距离。 
    }                     //最后加上路径压缩. 
    inline void merge(int x,int y)
    {
        f[x]=y;        
        dis[x]=size[y];
        size[y]+=size[x];
    }
    int main()
    {
        freopen("1.in","r",stdin);
        t=read();
        for(int i=1;i<=30000;i++) f[i]=i,size[i]=1;
        for(int i=1;i<=t;i++)
        {
            cin>>ch;
            int x=read(),y=read();
            int t1=getf(x);
            int t2=getf(y);
            if(ch=='M') merge(t1,t2);
            if(ch=='C') 
            {
                if(t1==t2) put(abs(dis[x]-dis[y])-1),cout<<endl;
                else       put(-1),cout<<endl;
            }
        }
        return 0;
    }
    View Code

     

    感觉这个题才让我懂得了并查集的真正含义:并查集能够确定记录并合并两者的关系.

    比如说这道题,通过简单的分析我们知道,开着的灯的两个开关必须总和为偶数次,才能保证这个灯继续开着.而闭着的灯必须总和为奇数次才能把灯打开...

    这就要需要我们记录两个开关的和为奇偶关系,这是我们把每个开关拆成两个点,奇数和偶数即可.

    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e5+10;
    int f[N<<1],n,m,a[N];//1倍表示奇数次,2表示偶数次. 
    vector<int>v[N];
    inline int read()
    {
        int x=0,ff=1;
        char ch=getchar();
        while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
        while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
        return x*ff;
    }
    inline int getf(int k){return k==f[k]?k:f[k]=getf(f[k]);}
    int main()
    {
        freopen("1.in","r",stdin);
        n=read();m=read();
        for(int i=1;i<=n;++i) a[i]=read();
        for(int i=1;i<=m;++i)
        {
            int x=read();
            for(int j=1;j<=x;++j)
            {
                int c=read();
                v[c].push_back(i);
            }
        }
        for(int i=1;i<=m<<1;++i) f[i]=i;
        for(int i=1;i<=n;++i)
        {
            int t1=getf(v[i][0]);//左奇 
            int t2=getf(v[i][0]+m);//左偶 
            int t3=getf(v[i][1]);//右奇 
            int t4=getf(v[i][1]+m);//右偶 
            if(!a[i])
            {
                if(t1==t3)     {printf("NO");return 0;}
                else f[t1]=t4,f[t2]=t3;
            }
            else 
            {
                if(t1==t4) {printf("NO");return 0;}
                else f[t1]=t3,f[t2]=t4;
            }
        }
        printf("YES");
        return 0;
    }
    View Code
  • 相关阅读:
    mysql5.6.20安装
    唯一识别Windows机器的最佳方法
    不用Root在安卓手机上运行Kali_Linux
    在Windows 7和10上显示上次登录帐户信息
    优化非活动窗口的颜色
    Windows10中以管理员身份打开命令提示符
    在Windows10中更改”WIN+E“快捷键打开目标
    在任务管理器中显示所有CPU内核性能
    Windows启动控制台登录模式
    Fluent Terminal
  • 原文地址:https://www.cnblogs.com/gcfer/p/10603252.html
Copyright © 2011-2022 走看看