深谈并查集

我们理解并查集这个数据结构的时候不要过于死板

我们要知道

并查集是用来维护关系的,而不是单纯一味去归,并,归,并,归,并

以前我就是一味的只知道 归,并,归,并

要深刻理解只有通过做题来打磨

https://www.luogu.org/problem/P2502

吐槽:这道题把我坑惨了

花了半晚上去做,最后发现我的思路是错的

应该开始看数据范围的时候就该察觉了

说到底还是对并查集理解不够深刻

分析:

先对边进行排序

再n2枚举,跑生成树跟新答案

开始枚举的i一定是minn

最后枚举到find(s)==find(t)时j就maxx

不断跟新答案

直到再也无法S与T连通为止

code:

#include <cstdio>
#include <algorithm>
#define maxn 600
#define maxm 5010
using namespace std;
int n,m,s,t;
int father[maxn];
int ans1,ans2;
struct rec
{
    int a,b,len;
} c[maxm];
bool cmp(rec a,rec b)
{return (a.len<b.len);}
int getfather(int x)
{
    if (father[x]==x) return x;
    return father[x]=getfather(father[x]);
}
int gcd(int x,int y)
{
    if (y>x) return gcd(y,x);
    if (!y) return x;
    return gcd(y,x%y);
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++) scanf("%d%d%d",&c[i].a,&c[i].b,&c[i].len);
    scanf("%d%d",&s,&t);
    sort(c+1,c+1+m,cmp);
    for (int i=1;i<=m;i++)
    {
        int j;
        for (j=1;j<=n;j++) father[j]=j;
        for (j=i;j<=m;j++)
        {
            int fa,fb;
            fa=getfather(c[j].a); fb=getfather(c[j].b);
            if (fa==fb) continue;
            father[fa]=fb;
            if (getfather(s)==getfather(t)) break;
        }
        if ((i==1)&&(getfather(s)!=getfather(t))) 
        {
            printf("IMPOSSIBLE
");
            return 0;
        }
        if (getfather(s)!=getfather(t)) break;  
        if (ans1*c[i].len>=ans2*c[j].len) ans1=c[j].len,ans2=c[i].len;
    }
    int x=gcd(ans1,ans2);
    if (x==ans2) printf("%d
",ans1/ans2); else printf("%d/%d
",ans1/x,ans2/x);
    return 0;
}

https://www.luogu.org/problem/P1892

吐槽:被绿题虐成狗了

分析:

我朋友的朋友是我的朋友;

我敌人的敌人也是我的朋友。

肯定并查集了

如果两者是朋友那么直接合并就好

那两者是敌人怎么办?

又因为敌人的敌人是朋友!!!

考虑这里有个转折点,能够将该点的所有敌人都连向它

使之能够将所有的敌人都连接上

肯定在原图上连是不现实的(原图相连表示两者是朋友)

这里就要用到并查集的反集了

例如:

此时有n个点,1的反集就是n+1,2的反集就是n+2,....n的反集就是n*2

连边的时候如果u与v是敌人关系,那么就将u的反集连v,v的反集连u

对于本题而言最大的团伙数就是fa[i]=i的个数

注意此时枚举只能[1,n],反集只是我们添进去辅助的,最后不会算

code:

#include<cstdio>
#include<iostream>
using namespace std;
int n,m;
int flag;
int flag1[9999];
int f[2500];
int find(int x)
{
    if(f[x]!=x) 
    f[x]=find(f[x]);    
    return f[x];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n*2;i++)f[i]=i; 
    for(int i=1;i<=m;i++) 
    {
        char t;int x,y;
        cin>>t>>x>>y;
        if(t=='F')      f[find(x)]=find(y);//朋友直接合并 
        if(t=='E')
        {
            f[find(x+n)]=find(y);//是敌人 
            f[find(y+n)]=find(x);//把反集合并 
        }
    }
    int s=0;
    for(int i=1;i<=n;i++)
    if(f[i]==i) s++;//找有多少祖先,就是有多少团伙 
    printf("%d",s);
    return 0;
} 

https://www.luogu.org/problem/P1525

以前做这题是似懂非懂

现在有了反集,就好做多了

分析:

从大到小操作,遇到和敌人相连了就输出,结果保证最优

code by wzxbeliever:

#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ri register int
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=20005;
const int maxm=100005; 
int n,m,ans;
int fa[maxn<<1];
struct node{
	int u,v,w;
}edg[maxm];
il int find(int x){if(fa[x]!=x)return fa[x]=find(fa[x]);return x;}
il bool cmp(node a,node b){return a.w> b.w;}
int main(){
	scanf("%d%d",&n,&m);
	for(ri i=1;i<=(n<<1);i++)fa[i]=i;
	for(ri i=1;i<=m;i++)scanf("%d%d%d",&edg[i].u,&edg[i].v,&edg[i].w);
	sort(edg+1,edg+1+m,cmp);
	for(ri i=1;i<=m;i++){
		int u=edg[i].u,v=edg[i].v;
		int fu=find(u),fv=find(v),ffu=find(u+n),ffv=find(v+n);
		if(fu!=fv&&ffu!=ffv)
		fa[fu]=ffv,fa[fv]=ffu;
		else {printf("%d
",edg[i].w);return 0;}
		}
		printf("0
");//细节特判
	return 0;
}

https://www.luogu.org/problem/P1640

上次AC这道题是用二分图匹配

但实际上这题可以用并查集来做

分析:

考虑一个武器两个属性(a,b),ab之间建边

对于一个连通块的大小(点数)为k

一:如果它有一个环

那么这整个连通块都可选上

二:如果它是一颗树(没有环)

那么去掉一个点后,剩余的都可以选上(这里显然是满足题意去掉最大的那个)

如果不太理解画图手动分析一下就行了

以上两点就是本题的关键之处,真的是妙啊妙啊

code:

#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ri register int
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=1000005;
int n;
int fa[maxn],sz[maxn]; 
bool cir[maxn];
il int find(int u){
     if(u!=fa[u])return fa[u]=find(fa[u]);
     return u;
}
int main(){
	scanf("%d",&n);
	for(ri i=1;i<=n+1;i++)fa[i]=i,sz[i]=1;
	for(ri i=1,u,v;i<=n;i++){
		scanf("%d%d",&u,&v);
		int fu=find(u),fv=find(v);
	   if(fu==fv)cir[fu]=cir[fv]=1;
	   else{
	   	cir[fu]=cir[fu]|cir[fv];
		    fa[fu]=fv;
		    sz[fv]+=sz[fu];
			sz[fu]=0;
	   }
	}
	for(ri i=1;i<=n+1;i++)
	if(!cir[find(i)]){
		if(sz[find(i)]>1)sz[find(i)]--;
		else {printf("%d
",i-1);break;}
	}
	return 0;
}

https://www.luogu.org/problem/P1196

好早以前就做过,就当复习一下带权并查集

code :

#include<bits/stdc++.h>
using namespace std;
const int maxn=30005;
int fa[maxn],front[maxn],num[maxn];
int x,y,i,j,n,T,ans;
char ins;
int find(int n){                                      
    if(fa[n]==n)return fa[n];
    int fn=find(fa[n]);                             
    front[n]+=front[fa[n]];    
    return fa[n]=fn;
}
int main(){
    cin>>T;
    for(i=1;i<=maxn;++i)fa[i]=i,front[i]=0,num[i]=1;
    while(T--){
        cin>>ins>>x>>y;
        int fx=find(x);                                
        int fy=find(y);                                
        if(ins=='M'){
            front[fx]+=num[fy];       
            fa[fx]=fy;                                
            num[fy]+=num[fx];                            
            num[fx]=0;                      
        }
        if(ins=='C'){
            if(fx!=fy)cout<<"-1"<<endl;           
else cout<<abs(front[x]-front[y])-1<<endl;   
        }
    }
    return 0;
}

https://www.luogu.org/problem/P2342

和上一道题类似

带权并查集

唯一要注意的是

输出答案时还要再查找一次

不是为了找祖先,而是为了跟新路径

code by wzxbeliever:

#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ri register int
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=500005;
int fa[maxn],sz[maxn],down[maxn]; 
int n;
il int find(int x){
	if(fa[x]==x)return x;
	int fn=find(fa[x]);
	down[x]+=down[fa[x]];
	return fa[x]=fn;
}
int main(){
	scanf("%d",&n);
	for(ri i=1;i<=n;i++)fa[i]=i,sz[i]=1,down[i]=0;
	for(ri i=1;i<=n;i++){
		char ch;cin>>ch;
		if(ch=='M'){
			int x,y;scanf("%d%d",&x,&y);
			int fx=find(x);
			int fy=find(y);
			if(fy!=fx) {
				fa[fx]=fy;
			down[fx]=sz[fy];
			sz[fy]+=sz[fx];
			sz[fx]=0;
			}
		}
		else {
			int x;scanf("%d",&x);find(x);//再查询一次是为了跟新而不是真正意义上的查找祖先 
			printf("%d
",down[x]);
		}
	}
	return 0;
}

原文地址:https://www.cnblogs.com/wzxbeliever/p/11768464.html