并查集 学习笔记

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

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

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

初始化: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;
}

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

posted @ 2020-03-12 16:49  我亦如此向往  阅读(205)  评论(0编辑  收藏  举报