深谈并查集

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

我们要知道

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

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

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

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

吐槽:这道题把我坑惨了

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

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

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

分析:

先对边进行排序

再n^2^枚举,跑生成树跟新答案

开始枚举的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\n");
            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\n",ans1/ans2); else printf("%d/%d\n",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\n",edg[i].w);return 0;}
        }
        printf("0\n");//细节特判
    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\n",i-1);break;}
    }
    return 0;
}

后续:妙不可言

猜你喜欢

转载自www.cnblogs.com/wzxbeliever/p/11768464.html
今日推荐