题目大意:
已知所有元素要么属于第一个集合,要么属于第二个集合,给出两种操作。第一种是D a b,表示
a,b两个元素不在一个集合里面。第二种操作是A a b,表示询问a,b两个元素是否在同一个集合
里面。如果不能确定的话打印not sure。否则给出答案。
题目分析:平常我们做的并查集都是用来放相同集合的,这道题给我们不同集合的。这题有两种方法,一种是开两倍的数组,另一种就是种类并查集
方法一:
开2*n的数组,i用来表示编号为i的元素,i+n表示与i对立的元素。在题目给定的输入,x与y属于不同的种类,由于只有两种种类,所以x对立的元素一定与y同种类,y对立的元素一定与x同种类。这样就可以通过union(x,y+n) union(x+n,y)来表示x,y在不同的两个种类中。这样合并就可以保证两个有相同对立的元素一定可以分配在同一个集合中
#include <cstdio>
int parent[200010];
int find(int p)
{
if( parent[p] != p ) parent[p] = find(parent[p]);
return parent[p];
}
void unionSet(int x,int y)
{
int rootx = find(x);
int rooty = find(y);
parent[rootx] = rooty;
}
int main()
{
int t;
scanf("%d",&t);
while( t-- )
{
int n,m;
scanf("%d%d",&n,&m);
for( int i = 1 ; i <= n ; i++ )
{
parent[i] = i;
parent[i+n] = i+n;
}
for( int i = 1 ; i <= m ; i++ )
{
char kind;
int x,y;
scanf("\n%c",&kind);
scanf("%d%d",&x,&y);
if( kind == 'A' )
{
int rootx = find(x);
int rooty = find(y);
int eremyy = find( y+n ); //找y的对立元素
if( rootx != eremyy && rootx != rooty ) printf("Not sure yet.\n"); //如果x和y不在一个集合,和y的对立面也不在一个集合,那么说明还没处理过。
else if( rootx == rooty ) printf("In the same gang.\n");
else printf("In different gangs.\n");
}else if( kind == 'D' )
{
unionSet(x,y+n);
unionSet(x+n,y);
}
}
}
return 0;
}
方法二 种类并查集
种类并查集也称为带权并查集。记录节点与父亲节点的关系权值。
种类并查集只是在并查集的基础上加了rela数组,用来表示元素与其父亲节点的关系。rela[i] == 0 表示它与父亲节点是同一类型的,rela[i] == 1 表示它与父亲节点不是一个类型的。
先来讲一下对find函数的改变:由于find函数中使用的是路径压缩,那么我们就可以在路径压缩的递归调用中更新rela数组的值。这里需要对路径压缩的过程有一个直观的理解。在递归调用中,我们总是把根节点的孙子节点直接与根节点相连,由于我们rela数组的定义,我们必须要更新rela数组来表示它与根节点的关系。
这样问题就变成了求一个节点与其爷爷节点的关系,为了解决这个问题,我们需要它的父亲节点的信息。用穷举法我们就可以得到(你们可以自己试一下,记住rela数组的含义),x与其爷爷的关系就会等于x与其父亲的关系加上其父亲与爷爷的关系对2求余。同样如果是3中类型的话就对3求余。这样find函数就会变成下面这样:
int find(int p)
{
if( parent[p] != p )
{
int t = parent[p];
parent[p] = find(parent[p]);
rela[p] = ( rela[p] + rela[t] ) % 2;
}
return parent[p];
}
好了现在来讲一下union函数的变化:union(x,y),这个只告诉了我们x与y是不一样的类型。这里需要注意的是由于路径压缩了之后,x与根最多只有父节点的关系。这里我们把x的根合并到y的根上,我们只需要更新x的根即可。而我们现在,有x与y的关系,x的父节点与x的关系,y的父节点与y的关系,现在要求的是x的父节点与y的父节点的关系。这时候我们就可以把x与y看作是父节点和儿子节点的关系,这样我们就可以找到x的根节点和y的根节点的关系。
同样的我们可以推出rela[rootx] = ( rela[x] + 1 + rela[y] ) % 2;
void unionSet(int x,int y)
{
int rootx = find(x);
int rooty = find(y);
parent[rootx] = rooty;
rela[rootx] = ( rela[x] + 1 + rela[y] ) % 2;
}
这样我们在原来并查集的基础上加了三行代码加一个数组,就可以完美的实现种类并查集了,但是原理蛮复杂的,需要慢慢消化。最后ac代码
#include <cstdio>
using namespace std;
int parent[100005];
int rela[100005];
int find(int p)
{
if( parent[p] != p )
{
int t = parent[p];
parent[p] = find(parent[p]);
rela[p] = ( rela[p] + rela[t] ) % 2;
}
return parent[p];
}
void unionSet(int x,int y)
{
int rootx = find(x);
int rooty = find(y);
parent[rootx] = rooty;
rela[rootx] = ( rela[x] + 1 + rela[y] ) % 2;
}
int main()
{
int t;
scanf("%d",&t);
while( t-- )
{
int n,m;
scanf("%d%d",&n,&m);
for( int i = 1 ; i <= n ; i++ )
{
parent[i] = i;
rela[i] = 0;
}
for( int i = 1 ; i <= m ; i++ )
{
char kind;
int x,y;
scanf("\n%c",&kind);
scanf("%d%d",&x,&y);
if( kind == 'A' )
{
int rootx = find(x);
int rooty = find(y);
if( rootx == rooty )
{
if( rela[x] == rela[y] ) printf("In the same gang.\n");
else printf("In different gangs.\n");
}else printf("Not sure yet.\n");
}else if( kind == 'D' ) unionSet(x,y);
}
}
return 0;
}