zoukankan      html  css  js  c++  java
  • 并查集 学习笔记

    此篇笔记是我从自己的洛谷博客上搬运而来。更多的是偏向于做题的总结。

    前言:简而言之,并查集是一种数据结构,带有一些限定条件,能够帮助计算机在很大的数据范围里很快得出结果。

    此算法可以理解为”父亲“和”儿子“的关系。一个父亲可以有多个儿子,每个儿子只有一个父亲。

    初始化:fa[i]=i。每个节点一开始的父亲都是他自己。

    查找函数 

    int find(int x){if ((if (fa[x]==x) return x;return fa[x]=find(fa[x]);} 

    这里需要路径压缩,为了更快的找出一个节点的父亲。

    合并函数

    void merge(int x,int y)
    {
      int xx=find(x),yy=find(y);
      if(xx!=yy) fa[xx]==yy;  
    }

    到这里,并查集的基础知识已经结束了。根据我的个人理解,并查集可以在多种类型的题目中出现,是一种非常有用的算法。很多图论题也可以用此算法AC。

    ----------------------------------------------

    T1 修复公路

    题目描述

    并查集裸题,其实就是最小生成树。

    #include<bits/stdc++.h>
    using namespace std;
    int father[100005],cnt[100005];
    struct node
    {
        int x,y,t;
    }s[100005];
    int  cmp(node s,node y)
    {
        return s.t<y.t; 
    }
    int find (int x)
    {
        if (x==father[x]) return x;
        return find(father[x]);;
    }
    int unionn(int r1,int r2)
    {
        father[r2]=r1;
    }
    int main()
    {
        int n,m;
        cin>>n>>m;
        for (int i=1;i<=n;i++) father[i]=i,cnt[i]=1;
        for (int i=1;i<=m;i++)
        {
            cin>>s[i].x>>s[i].y>>s[i].t;
        }
        sort(s+1,s+m+1,cmp);
        for (int i=1;i<=m;i++)
        {
            int r1=find(s[i].x),r2=find(s[i].y);
            if (r1!=r2) unionn(r1,r2),cnt[r1]+=cnt[r2];
            if (cnt[r1]==n){
                cout<<s[i].t;
                return 0;
            }
        }
        cout<<-1;
        return 0;
    }

    T2 关押罪犯

    题目描述

    一道很经典的题目。正解有两种,一种是二分图,另一种是并查集。

    这里我们要引入”补集“思想。正所谓敌人的敌人就是朋友。所以面对”敌人“,我们只需要将其和”敌人的敌人“进行合并就行了。

    #include<bits/stdc++.h>
    using namespace std;
    int f[200005];
    struct node
    {
        int a,b,c;
    }s[100005];
    int cmp(node x,node y)
    {
        return x.c>y.c;
    }
    int find (int x)
    {
        if (x==f[x]) return x;
        return find(f[x]);
    }
    int main()
    {
        int n,m;
        cin>>n>>m;
        for (int i=1;i<=n*2;i++) f[i]=i;
        for (int i=1;i<=m;i++)
            cin>>s[i].a>>s[i].b>>s[i].c;
        sort(s+1,s+m+1,cmp);
        for (int i=1;i<=m;i++)
        {
            int r1=find(s[i].a);
            int r2=find(s[i].b);
            if (r1==r2){
                cout<<s[i].c;return 0;
            }
            f[r2]=find(s[i].a+n);
            f[r1]=find(s[i].b+n);
        }
        cout<<0;
        return 0;
    }

    T3 食物链

    题目描述

    同样可以运用”补集“思想。这道题有三类物种:天敌,自己,猎物。在合并的时候还要注意是否矛盾,不能出现“自己的猎物是天敌”的情况。

    #include<bits/stdc++.h>
    using namespace std;
    int father[150005],ans;
    int find (int x)
    {
        if(x==father[x]) return x;
        return father[x]=find(father[x]);
    }
    inline void uni(int r1,int r2)
    {
        father[find(r2)]=find(r1);
    }
    int main()
    {
        int n,m;
        cin>>n>>m;
        for (int i=1;i<=3*n;i++) father[i]=i;
        for (int i=1;i<=m;i++)
        {
            int t,x,y;
            cin>>t>>x>>y;
            if (x>n||y>n)
            {
                ans++;
                continue;
            }
            if (t==1)
            {
                if (find(x+n)==find(y)||find(x+n*2)==find(y))
                {
                    ans++;
                    continue;
                }
                uni(x,y);uni(x+n,y+n);uni(x+2*n,y+2*n);
            }
            if (t==2)
            {
                if (find(x)==find(y)||find(x+2*n)==find(y))
                {
                    ans++;continue;
                }
                uni(x,y+2*n);uni(x+2*n,y+n);uni(x+n,y);
            }
        }
        cout<<ans;
        return 0;
    }

    T4 银河英雄传说

    题目描述

    同样是并查集,不过这道题要记录一下舰队的长度,来进行“首尾相接”的操作。

    #include<bits/stdc++.h>
    using namespace std;
    int f[30001],s[30001],b[30001];
    int find(int o)
    {
        if(f[o]==o) return o;
        int k=f[o];
        f[o]=find(f[o]);
        s[o]+=s[k];
        b[o]=b[f[o]];
        return f[o];
    }
    int main()
    {
        int n;
        cin>>n;
        for(int i=1;i<=30000;i++) {f[i]=i;s[i]=0;b[i]=1;}
        for(int i=1;i<=n;i++)
        {
            char ch;
            int x,y,dx,dy;
            cin>>ch>>x>>y;
            if(ch=='M')
            {
                dx=find(x);
                dy=find(y);
                f[dx]=dy;
                s[dx]+=b[dy];
                b[dx]+=b[dy];
                b[dy]=b[dx];
            }
            if(ch=='C')
            {
                dx=find(x);
                dy=find(y);
                if(dx!=dy){cout<<-1<<endl;continue;}
                cout<<abs(s[x]-s[y])-1<<endl;
            }
        }
        return 0;
    }

    T5 星球大战

    题目描述

    并查集+连通块。在进行完所有合并操作之后扫一下fa[],看如果有不同的数字出现ans++。最后ans即为答案。另外,这道题可能要利用逆向思维。

    #include<bits/stdc++.h>
    using namespace std;
    int fa[400005],ans[400005],broken[400005],Broken[400005];
    vector<int> v[400005];
    int find(int x)
    {
        if (x==fa[x]) return x;
        return fa[x]=find(fa[x]);
    }
    inline bool quary(int x,int y)
    {
        return find(x)==find(y);
    }
    inline void merge(int x,int y)
    {
        fa[find(x)]=find(y);
    }
    int main()
    {
        int n,m,tot;
        cin>>n>>m;
        tot=n;
        for (int i=1;i<=n;i++) fa[i]=i;
        for (int i=1;i<=m;i++)
        {
            int x,y;
            cin>>x>>y;
            v[x].push_back(y);
            v[y].push_back(x);
        }
        int k;cin>>k;
        for (int i=1;i<=k;i++)
        {
            cin>>broken[i];
            Broken[broken[i]]=1;
        }
        for (int i=0;i<n;i++)
        {
            if (!Broken[i])
                for (int j=0;j<v[i].size();j++)
                    if (!quary(i,v[i][j])&&!Broken[v[i][j]])
                    {
                        merge(i,v[i][j]);
                        tot--;
                    }
        }
        tot-=k;
        ans[k]=tot;
        for (int i=k;i>=1;i--)
        {
            tot++;
            Broken[broken[i]]=0;
            for (int j=0;j<v[broken[i]].size();j++)
                if (!quary(broken[i],v[broken[i]][j])&&!Broken[v[broken[i]][j]])
                    merge(broken[i],v[broken[i]][j]),tot--;
            ans[i-1]=tot;
        }
        for (int i=0;i<=k;i++)
            cout<<ans[i]<<endl;
        return 0;
    }

    后记:并查集类的题一般思维都比较巧妙,本身实现并不太难,想明白了发现其实也就那样。难的是几种算法综合在一起,需要一定的思维和代码能力。

  • 相关阅读:
    springMVC3学习(二)--ModelAndView对象
    springMVC3学习(一)--框架搭建
    JS作用域
    JS阻止事件冒泡
    Oracle常用函数
    Oracle中复制表结构和表数据
    转:JavaBean 、 Serverlet 总结
    form插件ajaxForm和ajaxSubmit方法传递对象参数说明
    http status 汇总
    浅谈HTTP中Get与Post的区别
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/12470287.html
Copyright © 2011-2022 走看看