http://poj.org/problem?id=1182
并查集的灵活应用
代码:
/*
可以这样理解
并查集是由很多树组成的,这些树不断的合并
下面代码f[]仍然代表此节点所属的树根
而d[]表示此点到父节点的差值,但是每次求fx()(树根)时,节点都更新指向树根
假如比父节点大1 则此点可以吃掉父节点
假如比父节点大2 则此点可以被父节点吃掉
假如和父节点相等,则属于同类
这里所指的大是针对当前节点对父节点而言的,2比1大1 0比2大1(循环)
由于更新完都指向树根了,所以就有了相同的参照物
根据不同的点和树根的关系,可以推算出一棵树任意两点之间的关系
问题就在于如何维护树的合并,f[]还是按照原来的方法,d[]需要根据实际情况取余
理解是注意自己画图
-----见代码注释
*/
#include<cstdio>
using namespace std;
const int N=50005;
int f[N],d[N];
int fx(int x)
{
if(f[x]!=x)
{
int tmp=f[x];
f[x]=fx(f[x]);
//更新到这来时,x的父节点已经更新完成 指向根节点
//而且x的父节点和根节点直接的关系也更新完成
//结下了要更新x节点 因为x节点已经也指向了根节点(通过给f[x]赋值)
//所以要根据父节点tmp到根节点的关系和x节点到父节点的关系
//推算出x节点到根节点的关系,下面的式子就是计算过程 注意对3取余
d[x]=(d[x]+d[tmp])%3;
}
return f[x];
}
int main()
{
//freopen("data.in","r",stdin);
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i)
{f[i]=i;d[i]=0;}//初始化d[i]=0 因为每个节点开始指向自己 和自己是同类
int ans=0;
while(m--)
{
int a,b,w,A,B;
scanf("%d %d %d",&w,&a,&b);
if(a>n||b>n||a<=0||b<=0)
{++ans;continue;}
A=fx(a);
B=fx(b);
//这时候通过 fx(a),fx(b) a和b都更新完成,d[a]和d[b]都表示和各自根节点(可能相同,可能不同)关系
if(w==1)
{
if(A==B)//假如 a和b 属于同一颗树
{
if(d[a]!=d[b])//如果不是同一类,矛盾
++ans;
}else//假如不是同一颗树
{
f[A]=B;//合并两棵树
//必须保证合并后d[a]和d[b]和树根B的关系一样
//那么就得满足d[a]+d[A]==d[b](%3) 所以。。。
d[A]=(d[b]+3-d[a])%3;
}
}else
{
if(A==B)//假如 a和b 属于同一颗树
{
if((d[b]+1)%3!=d[a])//则d[a] 比 d[b] 大1 否则矛盾
++ans;
}else//假如不是同一颗树
{
f[A]=B;//合并
//同样的原理 只不过要多加1 因为a吃b
d[A]=(d[b]+4-d[a])%3;
}
}
}
printf("%d\n",ans);
return 0;
}
浙公网安备 33010602011771号