• 深谈并查集


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

    我们要知道

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

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

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

    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;
    }
    
    
  • 相关阅读:
    实习工作两月
    远程mysql_java.sql.SQLException: null, message from server: "Host 'xxx' is not allowed to connect
    大学毕业后拉开差距的真正原因写给将要毕业的自己
    var nameValue=$("#"+name+"DelFlag_"+id).attr("name"); 中的nameValue是一个字符串!并非boolean值
    装饰模式
    数据结构图(非带权图)(js)
    WPF的逻辑树与视觉树(1)基本概念
    WPF的动画(2)Animation
    集合附加属性(HACK)
    WPF的逻辑树与视觉树(2)Visual容器
  • 原文地址:https://www.cnblogs.com/wzxbeliever/p/11768464.html
Copyright © 2020-2023  润新知