http://162.105.81.212/JudgeOnline/problem?id=1703
题意是有两个集合每次输入D a b 表示a跟b不在一个集合,输入A a b 询问a和b是否在一个集合。
以前做过这样的题目(PKU 2492),做法是用一个数组opt[t]存放t的对立,这样每次可以Union(a,opt[b]),Union(b,opt[a]),这样每次可以把相同的类合并。
这道题还有一种做法是把这些点都合并到一个集合,可以用该点到其根结点的距离来表示与根结点的关系。这个题中可以用距离的奇偶性分类,来一组点a ,b,就把b连到a后面,这样到根结点的距离为奇数的在一个集合,为偶数的在另一个集合。思路出来了,当合并时如何更新其对于根的关系是个问题,这点纠结了很久,后面请教了liuzhe,跟他讨论了一下,才有些思路,右参考了下网上的代码,唉,BS一下自己~。
当我们合并a,b时要把b接到a后面(这里没用按秩合并),合并时是要合并其根节点ra,rb,根据a与b的关系我们可以求出合并后rb与其根ra的关系,level[i]表示i到其根结点的距离,则level[rb]=(level[a]-level[b]+2)%2。修改了level[rb],则其依附于它的所有点的level[i](在这个集合中的关系)就要相应改变,这可以在每次查找时来更新它到依附点的关系,路径压缩后所有点都连到其根结点了,所以合并时这棵数最大高度为2,很方便更新与依附节点的关系:level[s]=(level[temp]+level[s])%2;即若其依附节点变为1,则该被依附点就要更新,否则说明其关系没有发生改变。
具体实现:
#include "stdio.h"
int f[100005],level[100005];
int test,n,m,ra,rb;
char c[2];
void creat_set()
{
for(int i=1;i<=n;i++)
{
f[i]=i;
level[i]=0;
}
}
int find_set(int s)
{
int temp;
if(s!=f[s])
{
temp=f[s];
f[s]=find_set(f[s]);
level[s]=(level[temp]+level[s])%2; // 更新其与依附节点的关系
}
return f[s];
}
void Union(int a,int b)
{
f[rb]=ra;
level[rb]=(level[a]-level[b]+1)%2; //修改b所在集合接到a所在集合后其根结点与新根结点的关系
}
int main()
{
int a,b;
scanf("%d",&test);
while(test--)
{
scanf("%d%d",&n,&m);
creat_set();
for(int i=1;i<=m;i++)
{
scanf("%s%d%d",c,&a,&b);
ra=find_set(a);
rb=find_set(b);
if(c[0]=='D')
Union(a,b);
else
{
if(ra!=rb)
printf("Not sure yet./n");
else if((level[a]+level[b])%2==0)
printf("In the same gang./n");
else
printf("In different gangs./n");
}
}
}
return 0;
}