并查集-并查集的扩展域

并查集所维护的是一种所属关系,通过并查集最快可以在O(1)的时间里查询到两者的关系,但有的时候,关系并不是特别简单。我们要维护除了关系外的一些别的东西。比如关系的类型,比如到根的距离等等。


例题一:食物链

动物王国中有三类动物 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 句话有的是真

的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

• 当前的话与前面的某些真的话冲突,就是假话

• 当前的话中 X 或 Y 比 N 大,就是假话

• 当前的话表示 X 吃 X,就是假话

你的任务是根据给定的 N 和 K 句话,输出假话的总数。

输入格式

第一行两个整数,N,K,表示有 N 个动物,K 句话。

第二行开始每行一句话(按照题目要求,见样例)

输出格式:一行,一个整数,表示假话的总数。

输入样例
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出样例
3

分析:一开始做这个题的时候,一直使用的是并查集补集的方法,与关押罪犯相似,开三倍的空间 1-N表示同类的,N+1-N*2表示他吃的,N*2+1-N*3表示吃他的。但是所有的并查集的扩展域的题目都是以这个题作为例题,因此就用这个题作为学习的例题。

与补集不同,这一种做法,是有祖先,但是还同时记录了这一个点和祖先之间的关系,在路径压缩维护时,同时会根据他的父节点来更新到祖先的关系。因此,在更新的时候,要提前记录父节点,然后根据父节点的关系,更新祖先的关系。

#include<cstdio>
#include<iostream>
using namespace std;
struct nodo {
    int pa;//表示祖先是谁 
    int re;//表示到祖先的关系 0:同类、1:被祖先吃、2:吃祖先 
};
nodo fa[50010];
int sumn,n,k;
int find(int ui){
        if(ui==fa[ui].pa) return ui;
        int last=fa[ui].pa;
        int grand=find(fa[ui].pa);
        if(fa[ui].re==0){
            if(fa[last].re==0) {fa[ui].re=0;return fa[ui].pa=grand;}
            if(fa[last].re==1) {fa[ui].re=1;return fa[ui].pa=grand;}
            if(fa[last].re==2) {fa[ui].re=2;return fa[ui].pa=grand;}
        }
        if(fa[ui].re==1){
            if(fa[last].re==0) {fa[ui].re=1;return fa[ui].pa=grand;}
            if(fa[last].re==1) {fa[ui].re=2;return fa[ui].pa=grand;}
            if(fa[last].re==2) {fa[ui].re=0;return fa[ui].pa=grand;}
        }
        if(fa[ui].re==2){
            if(fa[last].re==0) {fa[ui].re=2;return fa[ui].pa=grand;}
            if(fa[last].re==1) {fa[ui].re=0;return fa[ui].pa=grand;}
            if(fa[last].re==2) {fa[ui].re=1;return fa[ui].pa=grand;}
        }
}
void add(int xi,int yi,int wi){
    int f1=find(xi);
    int f2=find(yi);
    fa[f1].pa=f2;
    if(wi==1){
        if(fa[xi].re==0){
            if(fa[yi].re==0) {fa[f1].re=0;return ;}
            if(fa[yi].re==1) {fa[f1].re=1;return ;}
            if(fa[yi].re==2) {fa[f1].re=2;return ;}
        }
        if(fa[xi].re==1){
            if(fa[yi].re==0) {fa[f1].re=2;return ;}
            if(fa[yi].re==1) {fa[f1].re=0;return ;}
            if(fa[yi].re==2) {fa[f1].re=1;return ;}
        }
        if(fa[xi].re==2){
            if(fa[yi].re==0) {fa[f1].re=1;return ;}
            if(fa[yi].re==1) {fa[f1].re=2;return ;}
            if(fa[yi].re==2) {fa[f1].re=0;return ;}
        }
    }
    if(wi==2){
        if(fa[xi].re==0){
            if(fa[yi].re==0) {fa[f1].re=2;return ;}
            if(fa[yi].re==1) {fa[f1].re=0;return ;}
            if(fa[yi].re==2) {fa[f1].re=1;return ;}
        }
        if(fa[xi].re==1){
            if(fa[yi].re==0) {fa[f1].re=1;return ;}
            if(fa[yi].re==1) {fa[f1].re=2;return ;}
            if(fa[yi].re==2) {fa[f1].re=0;return ;}
        }
        if(fa[xi].re==2){
            if(fa[yi].re==0) {fa[f1].re=0;return ;}
            if(fa[yi].re==1) {fa[f1].re=1;return ;}
            if(fa[yi].re==2) {fa[f1].re=2;return ;}
        }
    }
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        fa[i].pa=i;
        fa[i].re=0;
    }
    while(k--){
        int ti,xi,yi;
        scanf("%d%d%d",&ti,&xi,&yi);
        if(xi>n||yi>n){
            sumn++;
            continue;
        }
        if(ti==1){
            int f1=find(xi);
            int f2=find(yi);
            if(f1==f2){
                if(fa[xi].re==fa[yi].re){
                    continue;
                }
                else{
                    sumn++;
                }
            }
            else{
                add(xi,yi,1);
            }
        }
         if(ti==2){
            int f1=find(xi);
            int f2=find(yi);
            if(f1==f2){
                if(fa[xi].re==1&&fa[yi].re==2) continue;
                if(fa[xi].re==0&&fa[yi].re==1) continue;
                if(fa[xi].re==2&&fa[yi].re==0) continue;
                sumn++;
            }
            else{
                add(xi,yi,2);
            }
        }
    }
    printf("%d",sumn);
}

例题二:统计错误答案

 题意:给出区间[1,n],下面有m组数据,l r v区间[l,r]之和为v,每输入一组数据,判断此组条件是否与前面冲突 ,最后输出与前面冲突的数据的个数.。

 分析:这个题的思路也是使用扩展域,设一个虚点作为根节点。【L,R】的值为4,就是相当于L到根的长度,比R到根的长度多4。类似于向量的做法,关键点是查询时要检查【L-1到R】的值。每次合并的时候,同时维护该点到根的长度。最后修改L的祖先,L到原来祖先的长度+原来祖先到新祖先的长度-R到新祖先的长度==[L-R]的值,以此来作为更新的依据。

#include<cstdio>
#include<iostream>
using namespace std;
struct nodo {
    int pr;
    int su;
};
nodo fa[200010];
int n,m,sumn;
int find(int ui){
    if(fa[ui].pr==ui) return ui;
    int past=fa[ui].pr;
    int grand=find(fa[ui].pr);
    fa[ui].su+=fa[past].su;
    return fa[ui].pr=grand;
}
int main(){
    
    while(scanf("%d%d",&n,&m)!=EOF){
        sumn=0;
        for(int i=0;i<=n;i++){
            fa[i].pr=i;
            fa[i].su=0;
        }
        while(m--){
            int xi,yi,si;
            scanf("%d%d%d",&xi,&yi,&si);
            xi--;
            int f1=find(xi);
            int f2=find(yi);
            if(f1==f2){
                if(fa[xi].su-fa[yi].su==si) continue;
                sumn++;
            }
            else{
                fa[f1].pr=f2;
                fa[f1].su=-fa[xi].su+fa[yi].su+si;//fa[yi]-fa[xi]==si 
            }
        }
        printf("%d\n",sumn);
    }
    return 0;
}

参考博客:

https://www.cnblogs.com/qq136155330/p/9395394.html

 https://www.cnblogs.com/liyinggang/p/5327055.htm

 
 

猜你喜欢

转载自www.cnblogs.com/wingman/p/10386596.html