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

  

 

posted on 2012-12-15 21:31  夜->  阅读(197)  评论(1编辑  收藏  举报