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; }