用并查集维护单词的集合,在同一个集合内的单词互有同义或反义的关系,在不同集合内的单词没有任何关系。
那么每个集合内的单词,可以分为两个类别,同一类别的互为同义,不同类别的互为反义。
我们可以用d[x]来表示这个类别,并在并查集递归与合并的过程维护d[x]即可。
由于不同集合内的单词可以事先判断出没有任何关系,所以所有单词都可以只用两种不同的记号来标记类别,取记号0,1就很方便。一是因为很顺手,二是因为可以用亦或运算改变类别,三是因为可以理解成false和ture。
从初识并查集到现在已经一年多了,零零碎碎地也碰过不少并查集的题目,但是要么就是根本没看出来是并查集,要么就是思路只停留在背模板的水平上。
ACM还是深似海的,小小的并查集都能有如此之多的变化。
目前还是认为专题训练+打比赛的学习方式比较正确,但感觉自己现在更需要打比赛或者刷散题。
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
const int maxn = 100010;
int fa[maxn];int d[maxn];
int f(int x)
{
if(fa[x]==x) return x;
int temp=f(fa[x]);
d[x]^=d[fa[x]];
return fa[x]=temp;
}
string STR[maxn];
map<string,int> INT;
int main()
{
int n,m,q;scanf("%d %d %d",&n,&m,&q);
rep(i,1,n)
{
cin>>STR[i];
INT[STR[i]]=i;
fa[i]=i;
}
while(m--)
{
int t;scanf("%d",&t);--t;
string sx,sy;
cin>>sx>>sy;
int idx=INT[sx],idy=INT[sy];
int x=f(idx),y=f(idy);
bool ok=true;
if(x==y)
{
if((d[idx]^d[idy])!=t)
ok=false;
}
else
{
fa[x]=y;
d[x]=d[idx]^d[idy]^t;
}
if(ok) puts("YES");
else puts("NO");
}
while(q--)
{
string sx,sy;
cin>>sx>>sy;
int idx=INT[sx],idy=INT[sy];
int x=f(idx),y=f(idy);
if(x!=y) puts("3");
else printf("%d\n",(d[idx]^d[idy])+1);
}
return 0;
}