首页 > 代码库 > POJ 1182 (经典食物链 /并查集扩展)

POJ 1182 (经典食物链 /并查集扩展)

(参考他人资料)

向量偏移——由“食物链”引发的总结

http://poj.org/problem?id=1182这道食物链题目是并查集的变型,很久以前做的一次是水过的,这次仔细地研究了这“食物链”,无非就是运用向量偏移,从以前节点与节点转化成向量与向量的关系。我们可以把矛盾的产生得益于向量偏移时的结果。

直接引出向量偏移的运用。

 

下面是POJ一位大牛这样理解的,本人稍有修改。

对于集合里的任意两个元素a,b而言,它们之间必定存在着某种联系,因为并查集中的元素均是有联系的,否则也不会被合并到当前集合中。那么我们就把这2个元素之间的关系量转化为一个偏移量,以食物链的关系而言,不妨假设

a->b 偏移量0时 a和b同类

a->b 偏移量1时 a吃b

a->b 偏移量2时 a被b吃,也就是b吃a

有了这些基础,我们就可以在并查集中完成任意两个元素之间的关系转换了。不妨继续假设,a的当前集合根节点aa,b的当前集合根节点bb,a->b的偏移值为d-1(题中给出的询问已知条件)


(1)如果aa和bb不相同,那么我们把bb合并到aa上,并且更新delta[bb]值(delta[i]表示i的当前集合根节点到i的偏移量)

此时 aa->bb = aa->a + a->b + b->bb,可能这一步就是所谓向量思维模式吧

上式进一步转化为:aa->bb = (3-delta[a]+d-1+delta[b])%3 = delta[bb],(模3是保证偏移量取值始终在[0,2]间)


以图示表示为:





(2)如果aa和bb相同,那么我们就验证a->b之间的偏移量是否与题中给出的d-1一致

 此时 a->b = a->aa + aa->b = a->aa + bb->b,

上式进一步转化为:a->b = (3+delta[a]-delta[b])%3,若一致则为真,否则为假。

 

以图示表示为:



 



一般化总结:

并查集的偏移向量属于并查集的变形,只要适用于集合数目较少,或是固定的并查集类型。


#include<iostream>
#include<cstdio>
#define maxn 50001
using namespace std;

int uset[maxn],rel[maxn];

int find_uset(int x)
{
    if(uset[x]!=x)
    {
        int k=uset[x];                  //先写
        uset[x]=find_uset(uset[x]);               //注意上下两处,因为递归调用循序的原因。
        rel[x]=(rel[k]+rel[x])%3;       //后写,
    }
    return uset[x];
}

int make_uset(int x,int y,int d)
{
    int ux,uy;
    if((ux=find_uset(x))==(uy=find_uset(y)))
    {
        if((3+rel[x]-rel[y])%3!=(d-1))
            return 1;
        return 0;
    }
    else
    {
        uset[ux]=uy;
        rel[ux]=(3-rel[x]+d-1+rel[y])%3;
        return 0;
    }
}


int main()
{
    int n,k;
    scanf("%d%d",&n,&k);

        int d,x,y,cnt=0;
        for(int i=1;i<=n;i++)
        {
            uset[i]=i;
            rel[i]=0;
        }
        for(int i=0;i<k;i++)
        {
            scanf("%d%d%d",&d,&x,&y);
            if(x>n||y>n||(d==2&&x==y))
                cnt++;
            else if(make_uset(x,y,d))
                cnt++;
        }

        printf("%d\n",cnt);



    return 0;
}