笔记并查集

Posted dprswdr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了笔记并查集相关的知识,希望对你有一定的参考价值。

描述:并查集是一种对集合进行合并、查询等一系列操作。时间复杂度为O(a(n)) 比O(logn)还快。

代码

技术分享图片
 1 int fa[sz];//集合数组 
 2 void init()//预处理  
 3 {
 4     for(int i=1;i<=n;i++)
 5     {
 6         fa[i]=i;//初始时每个点都是一个独立的集合
 7         rank[i]=0;//按秩合并 初始时每一个集合形成的树的高度为0; 
 8     }
 9 }
10 int find(int x)//查询操作 返回x所在集合的代表元素 
11 {
12     return fa[x]==x?x:fa[x]=find(fa[x]);
13     //如果x就是它所在集合的代表元素 就返回x
14     //反之 让它到代表元素上的点的父节点都变成代表元素 并返回修改后父节点的值 即代表元素
15     //路径压缩 使并查集更加高效 
16 }
17 void unite(int u,int v)//合并操作 
18 {
19     u=find(u),v=find(v);//对u和v的集合进行合并操作 因此将它们变成其所在集合代表元素 
20     if(u==v) return ;//如果u和v在同一集合内 则不必进行合并操作
21     //按秩合并 防止树退化成链 
22     if(rank[u]<rank[v])//如果u和v所在子树的rank不同 
23     {
24         fa[u]=v;//就将rank小的合并到rank大的上 
25     }
26     else 
27     {
28         fa[v]=u;
29         if(rank[u]==rank[v])
30             rank[u]++;
31     }
32 }
33 bool same(int u,int v)//判断两个元素是否属于同一集合 
34 {
35     u=find(u),v=find(v);
36     return u==v;
37 }
View Code

拓展及应用

①拓展域并查集

例题:

1.洛谷P1892 团伙

思路:每个节点维护两个域--朋友域fa[x]和敌人域fa[x+n],若u与v是敌人,u的敌人域等于v的朋友域,遂合并。

若u与v是朋友,就将两者的朋友域进行合并。最后看每个节点属于多少个不同的集合即为答案。

AC Code:

技术分享图片
 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 const int sz=1000+100;
 5 int fa[sz<<1];
 6 int find(int x)
 7 {
 8     return fa[x]==x?x:fa[x]=find(fa[x]);
 9 }
10 bool vis[sz<<1];
11 int main()
12 {
13     int n,m;
14     scanf("%d%d",&n,&m);
15     char c;int u,v;
16     for(int i=1;i<=(n<<1);i++)
17     {
18         fa[i]=i;
19     }
20     for(int i=1;i<=m;i++)
21     {
22         cin>>c>>u>>v;
23         if(c==F)
24         {
25             fa[find(u)]=find(v);
26         }
27         if(c==E)
28         {
29             fa[find(v)]=find(u+n);
30             fa[find(v+n)]=find(u);
31         }
32     }    
33     for(int i=1;i<=n;i++)
34         vis[find(i)]=true;
35     int ans=0;
36     for(int i=1;i<=(n<<1);i++)
37         if(vis[i]) ans++;
38     printf("%d",ans);
39     return 0;
40 }
View Code

2.P2024 食物链

思路:与上面的题目类似,对每个动物维护三个域:同类域fa[x],吃域fa[x+n],被吃域fa[x+n*2]。

若x与y是同类,就将它们的三个域分别合并;若x被y吃,则x的同类域等于y的吃域,x的吃域等于y的被吃域,x的被吃域等于y的同类域。

AC Code:

技术分享图片
#include<cstdio>
using namespace std;
const int sz=300000+500;
int fa[sz];
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=3*k;i++)
        fa[i]=i;
    int num,x,y,ans=0;
    for(int i=1;i<=k;i++)
    {
        scanf("%d%d%d",&num,&x,&y);
        if(x>n||y>n)//若x或y超过了范围,则为假话 
        {
            ans++;continue;
        }
        int x1=find(x),x2=find(x+n),x3=find(x+2*n);//x1=x的同类域 x2=x的吃域 x3=x的被吃域
        int y1=find(y),y2=find(y+n),y3=find(y+2*n); 
        if(num==1)//若x与y是同类 
        {
            if(x1==y2||x2==y1) ans++;//若存在吃与被吃关系 则为假话 
            else
            {
                fa[x1]=y1;fa[x2]=y2;fa[x3]=y3;//若不矛盾 则为真话 合并集合 
            } 
        }
        if(num==2)//若x吃y 
        {
            if(x1==y1||x1==y2) ans++;//若x与y是同类 或x被y吃 则为假话 
            else 
            {
                fa[x1]=y3;fa[x2]=y1;fa[x3]=y2;//反之,x的同类域等于y的吃域,x的吃域等于y的被吃域,x的被吃域等于y的同类域 
            }
        }
    }
    printf("%d",ans);
    return 0;
}
View Code

②带权并查集

学习ing 以后再填。。。

①最小生成树 MST

 并查集是Kruskal的灵魂。

例题:

P2330 [SCOI2005]繁忙的都市

思路:Kruskal大家应该都会吧....

AC Code:

技术分享图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int sz=50000+50;
 6 int n,m;
 7 struct node{
 8     int x,y,w;
 9 }e[sz*2];
10 bool cmp(node a,node b)
11 {
12     return a.w<b.w;
13 }
14 int fa[sz];
15 int find(int x)
16 {
17     return fa[x]==x?x:fa[x]=find(fa[x]);
18 }
19 int ans;
20 void kruskal()
21 {
22     ans=-999;
23     for(int i=1;i<=m;i++)
24     {
25         int x=e[i].x,y=e[i].y;
26         if(find(x)!=find(y))
27         {
28             fa[find(x)]=find(y);
29             ans=max(ans,e[i].w);
30         }
31     }
32 }
33 int main()
34 {
35     scanf("%d%d",&n,&m);
36     for(int i=1;i<=m;i++)
37     {
38         int u,v,w;
39         scanf("%d%d%d",&u,&v,&w);
40         e[i].x=u,e[i].y=v,e[i].w=w;
41     }
42     sort(e+1,e+m+1,cmp);
43     for(int i=1;i<=n;i++)
44         fa[i]=i;
45     kruskal();
46     printf("%d %d",n-1,ans);
47     return 0;
48 }
View Code

②求最小环

.

例题:P2661 信息传递

思路:这道题可以DFS过,也可以用并查集做

AC Code:

③求满足要求的集合个数

看有多少x的fa[x]=x。

例题:P3420 [POI2005]SKA-Piggy Banks

思路:将钥匙相同的存钱罐连成一个大存钱罐,最后看大存钱罐的数量。

AC Code:

技术分享图片
 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 const int sz=1000000+100;
 5 int fa[sz];
 6 int find(int x)
 7 {
 8     return fa[x]==x?x:fa[x]=find(fa[x]);
 9 }
10 int main()
11 {
12     int n;
13     scanf("%d",&n);
14     for(int i=1;i<=n;i++)
15         fa[i]=i;
16     for(int i=1;i<=n;i++)
17     {
18         int k;
19         scanf("%d",&k);
20         int u=find(i),v=find(k);
21         fa[u]=v;        
22     }    
23     int cnt=0;
24     for(int i=1;i<=n;i++)
25         if(fa[i]==i)
26             cnt++;
27     printf("%d",cnt);
28     return 0;
29 }
View Code

④求LCA

End.

以上是关于笔记并查集的主要内容,如果未能解决你的问题,请参考以下文章

算法笔记:并查集

并查集学习笔记

算法笔记之并查集

算法笔记并查集你了解吗?

算法笔记并查集你了解吗?

数据结构 ---[实现并查集(UnionFind)]