并查集基础以及拓展应用(理解记忆了还怕并查集?)

最近老是做到并查集的题,趁着机会总结一下用法,今后遇到新题型继续补充博客,不得不说最近一天不敲代码手痒得慌啊!
可是自己太菜了根本记不到!可恶!记忆记忆!

基础并查集

初始化

 for(int i=0;i<=n;i++) f[i]=i;

查找

int get(int x){
    
    
 	if(x==f[x]) rturn x;
 	return f[x]=find(f[x]);//路径压缩 
 }

合并

void merge(int x,int y){
    
    
	fa[get(x)]=get(y);
}

边带权并查集

例题

题目描述
公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。
宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。
杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成30000列,每列依次编号为1, 2, …, 30000。之后,他把自己的战舰也依次编号为1, 2, …, 30000,让第i号战舰处于第i列(i = 1, 2, …, 30000),形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为M i j,含义为让第i号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第j号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。
然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。
在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j。该指令意思是,询问电脑,杨威利的第i号战舰与第j号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。
作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以及回答莱因哈特的询问。
最终的决战已经展开,银河的历史又翻过了一页……
输入描述:
第一行有一个整数T(1≤T≤500,000),表示总共有T条指令。

以下有T行,每行有一条指令。指令有两种格式:

M i j :i和j是两个整数(1≤i , j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第i号战舰与第j号战舰不在同一列。C i j :i和j是两个整数(1≤i , j≤30000),表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。

输出描述:
你的程序应当依次对输入的每一条指令进行分析和处理:

如果是杨威利发布的舰队调动指令,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息;

如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,第i号战舰与第j号战舰之间布置的战舰数目。如果第i号战舰与第j号战舰当前不在同一列上,则输出-1。

示例1
输入
4
M 2 3
C 1 2
M 2 4
C 4 2

输出
-1
1

查找

int get(int x){
    
    
	if(x==f[x]) return x;
	d[x]+=d[f[x]];//对边权求和 
	return f[x]=get(f[x]);
}

合并

void merge(int x,int y){
    
    
	x=get(x),y=get(y);
	fa[x]=y,d[x]=siz[y];
	siz[y]+=siz[x];
}

AC代码

#include<iostream>
#include<math.h>
using namespace std;

const int MAX = 50000;
int fa[MAX],d[MAX],siz[MAX];

int get(int x){
    
    
    if(x==fa[x]) return x;
    int root=get(fa[x]);
    d[x]+=d[fa[x]];
    return fa[x]=root;
}

void merge(int x,int y){
    
    
    x=get(x),y=get(y);
    fa[x]=y,d[x]=siz[y];
    siz[y]+=siz[x];
}

int main(){
    
    
    int t;
    scanf("%d",&t);
    
    for(int i=1;i<=30000;i++) fa[i]=i,d[i]=0,siz[i]=1;
    while(t--){
    
    
        char op[2];
        int i,j;
        scanf("%s%d%d",op,&i,&j);
        if(op[0]=='M') merge(i,j);
        else{
    
    
            if(get(i)!=get(j)){
    
    cout<<-1<<'\n';continue;}
            cout<<abs(d[j]-d[i])-1<<'\n';
        } 
    }
        
}

练习题

1.L2-007 家庭房产 (25 分)
博客解析


扩展域并查集

例题

题目描述
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B,B吃C,C吃A。

现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这N个动物所构成的食物链关系进行描述:

第一种说法是“1 X Y”,表示X和Y是同类。

第二种说法是“2 X Y”,表示X吃Y。

此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

1) 当前的话与前面的某些真的话冲突,就是假话;

2) 当前的话中X或Y比N大,就是假话;

3) 当前的话表示X吃X,就是假话。

你的任务是根据给定的N(1≤N≤50,000)和K句话(0≤K≤100,000),输出假话的总数。

输入描述:
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
输出描述:
只有一个整数,表示假话的数目。

输入
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5

输出
3

分析

将每个动物之间的关系转换为三个:

  • 同类动物
  • 天敌
  • 捕食

将他们之间的关系转换成并查集,分别是self域,enemy域,eat域,将在同一动物关系用并查集合并,不同的关系转换成:

  • self域:x //它自己
  • enemy域:x+n //天敌
  • eat域:x+2*n //食物

那么,x、y是同类和x吃y就可以转换为下面两个操作:

  • merge(x,y),merge(x+n,y+n),merge(x+2n,y+2n)
  • //x、y是同类,那么它的天敌和食物都是同类
  • merge(x+n,y),merge(x,y+2n),merge(x+2n,y+n)
  • //因为x吃y,x的天敌也是y的天敌,x的食物就是y,x也可以吃y的食物

AC代码

#include<iostream>
using namespace std;

int a[200000];

int get(int x){
    
    
    if(x==a[x]) return x;
    return a[x]=get(a[x]);
}

void merge(int x,int y){
    
    
    a[get(x)]=get(y);
}
int main(){
    
    
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n*3;i++) a[i]=i;
    int ans=0;
    while(k--){
    
    
        int d,x,y;
        cin>>d>>x>>y;
        if(x>n||y>n){
    
    ans++;continue;}
        if(d==1){
    
    
            if(get(x+n)==get(y)||get(x+2*n)==get(y)) ans++;
            else merge(x,y),merge(x+n,y+n),merge(x+2*n,y+2*n);
        }else{
    
    
            if(x==y||get(x)==get(y)||get(x+2*n)==get(y)) ans++;
            else merge(x+2*n,y+n),merge(x,y+2*n),merge(x+n,y);
        }
    }
    cout<<ans;
    return 0;
}

练习题

排座位

猜你喜欢

转载自blog.csdn.net/jigsaw_zyx/article/details/123285038