过去在做并查集的题目时,往往是将相同元素组成一个集合进行操作,最近,在练习中发现,原来并查集还可以操作类型不同的元素,具体来说,就是在统计敌对关系或者不同类型元素之间的关系时,比如食物链,也会用到并查集,但是分成多个集合是不现实的,因为元素之间的关系有可能交叉,因此,应该使用一个集合去操作
常用方法:每个元素除了有记录父节点的数组外,还要有一个记录与当前父节点的距离的数组,为什么说是当前呢?因为在并查集中最著名的就是通过指向根节点来压缩路径,因此在实际操作中,父节点会经常发生变化,而这个记录与当前父节点的距离的数组也要跟着变化,记录的所谓距离其实使用用来判断与父节点的关系的(具体看下面的例子),在操作过程中,这个记录距离的数组起着至关重要的作用
例1,关于敌对关系的并查集 poj1703
Description
The police office in Tadu City decides to say ends to the chaos, as launch actions to root up the TWO gangs in the city, Gang Dragon and Gang Snake. However, the police first needs to identify which gang a criminal belongs to. The present question is, given two criminals; do they belong to a same clan? You must give your judgment based on incomplete information. (Since the gangsters are always acting secretly.)
Assume N (N <= 10^5) criminals are currently in Tadu City, numbered from 1 to N. And of course, at least one of them belongs to Gang Dragon, and the same for Gang Snake. You will be given M (M <= 10^5) messages in sequence, which are in the following two kinds:
1. D [a] [b]
where [a] and [b] are the numbers of two criminals, and they belong to different gangs.
2. A [a] [b]
where [a] and [b] are the numbers of two criminals. This requires you to decide whether a and b belong to a same gang.
Assume N (N <= 10^5) criminals are currently in Tadu City, numbered from 1 to N. And of course, at least one of them belongs to Gang Dragon, and the same for Gang Snake. You will be given M (M <= 10^5) messages in sequence, which are in the following two kinds:
1. D [a] [b]
where [a] and [b] are the numbers of two criminals, and they belong to different gangs.
2. A [a] [b]
where [a] and [b] are the numbers of two criminals. This requires you to decide whether a and b belong to a same gang.
Input
The first line of the input contains a single integer T (1 <= T <= 20), the number of test cases. Then T cases follow. Each test case begins with a line with two integers N and M, followed by M lines each containing one message as described above.
Output
For each message "A [a] [b]" in each case, your program should give the judgment based on the information got before. The answers might be one of "In the same gang.", "In different gangs." and "Not sure yet."
Sample Input
1 5 5 A 1 2 D 1 2 A 1 2 D 2 4 A 1 4
Sample Output
Not sure yet. In different gangs. In the same gang.
题目大意是:A a b 是问你a和b是不是同一个帮派,D a b 告诉你a和b是不同的帮派
代码:
#include<stdio.h>
#include<string.h>
#define N 100005
int fa[N],dis[N];//fa记录父节点,dis记录到父节点的距离,用来判断与父节点的关系,0表示同帮派,1表示不同帮派
void init()
{
for(int i=0;i<N;i++)
{
fa[i]=i;
dis[i]=0;
}
}
int find_set(int x)
{
int fx=fa[x];
if(fx!=x)
{
fa[x]=find_set(fx);//回溯使得父节点变为根节点
dis[x]=(dis[x]+dis[fx])%2;//回溯时通过原父节点到根节点的距离更新子节点到根节点(也就是现在的父节点)的距离
}
return fa[x];
}
void Union(int x,int y)
{
int fx=find_set(x);
int fy=find_set(y);
if(fx==fy)
return ;
fa[fy]=fx;//合并时只合并根节点
dis[fy]=(dis[x]+1-dis[y])%2;//更新也只更新根节点,当子节点调用find_set时,会自动更新
}
int main()
{
int t,n,m,x,y;
char c[5];
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%s%d%d",c,&x,&y);
if(c[0]=='A')
{
int fx=find_set(x);
int fy=find_set(y);
if(fx!=fy)//不同集合不可比
printf("Not sure yet.\n");
else
{
if(dis[x]==dis[y])//到根结点距离相等的子节点为同一帮派
printf("In the same gang.\n");
else
printf("In different gangs.\n");
}
}
else
Union(x,y);
}
}
return 0;
}
例2,食物链关系,也就是多类元素的关系poj1182
Description
动物王国中有三类动物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个动物,以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),输出假话的总数。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
只有一个整数,表示假话的数目。
Sample Input
100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5
Sample Output
3
代码:
#include<stdio.h>
#include<string.h>
struct Animal
{
int dis;//到父节点的距离,用来判断该元素与父节点的关系,0代表同类,1代表被父节点吃,2代表吃父节点
int fa;//记录父节点
}a[51000];
void init()
{
for(int i=0;i<51000;i++)
{
a[i].dis=0;
a[i].fa=i;
}
}
int find_set(int x)
{
int fa=a[x].fa;
if(fa!=x)
{
a[x].fa=find_set(fa);//回溯使得父节点为集合根节点,使得到父节点的距离变为到根节点的距离
a[x].dis=(a[x].dis+a[fa].dis)%3;//通过回溯以及父节点更新该节点到根节点的距离
}
return a[x].fa;
}
void Union(int x,int y,int d)
{
int fx=find_set(x);
int fy=find_set(y);
a[fy].fa=fx;//要更新的是根节点,只有更新根节点才能通过find_set更新根节点下所有子节点
if(d==1)
a[fy].dis=(a[x].dis+3-a[y].dis)%3;//同样,只要更新根节点
else
a[fy].dis=(a[x].dis+4-a[y].dis)%3;
}
int main()
{
int d,x,y,n,k,c;
scanf("%d%d",&n,&k);
{
init();
c=0;
for(int i=0;i<k;i++)
{
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n)
{
c++;
continue;
}
int fx=find_set(x);
int fy=find_set(y);
if(fx==fy)//只有在同一集合的前提下才能够判断,不同集合不存在真假
{
if(d==1)
{
if(a[x].dis!=a[y].dis)
c++;
}
if(d==2)
{
if(x==y)
c++;
else if(a[x].dis!=(a[y].dis+2)%3)
c++;
}
}
else//不同集合就合并成一个集合,合并之后才会出现矛盾
Union(x,y,d);
}
printf("%d\n",c);
}
return 0;
}
上述两个例子很好的看出,尽管在一个集合中存在不同类型的元素,当通过到根结点的距离(一般有多少类型元素,距离的最大值为元素类型数减1)可以很好的把元素分类,而且距离这个概念很好理解,如果想不通更新过程,可以在纸上画两个子结点,两个根结点,并相应的连上线,再通过简单的加减法,就能推出更新的公式了