POJ 1182

来源:互联网 发布:淘宝怎么举报卖家假货 编辑:程序博客网 时间:2024/05/26 12:57

食物链

Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 20000/10000K (Java/Other)
Total Submission(s) : 2   Accepted Submission(s) : 0
Problem Description
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 
有人用两种说法对这N个动物所构成的食物链关系进行描述: 
第一种说法是"1 X Y",表示X和Y是同类。 
第二种说法是"2 X Y",表示X吃Y。 
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 
1) 当前的话与前面的某些真的话冲突,就是假话; 
2) 当前的话中X或Y比N大,就是假话; 
3) 当前的话表示X吃X,就是假话。 
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。 
 

 

Input
第一行是两个整数N和K,以一个空格分隔。 
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。 
若D=1,则表示X和Y是同类。 
若D=2,则表示X吃Y。
 

 

Output
只有一个整数,表示假话的数目。
 

 

Sample Input
100 71 101 1 2 1 22 2 3 2 3 3 1 1 3 2 3 1 1 5 5
 

 

Sample Output
3
 

 思路:种类并查集,并查集的变形,把访问过的点都加入到一个集合中,集合中的点与其根节点之间的关系可以用偏移量表示,delta[x]表示x与根节点之间的关系, delat[x] = 0 表示同类,= 1 表示a吃delta[a],= 2表示被吃。 这样一个集合中的点虽然在一个集合中但关系并不止一种(多种状态),区分他们之间的关系就是通过delta[x]与delta[y]之间的关系!

以前做过的关于并查集的题目,比如a和b是亲戚,b和c是亲戚,则a和c是亲戚的那种,只是将亲戚放在一个集合中,在一个集合中的点都是亲戚,即一个集合之间的点只有一种关系(一种状态),故容易判断,不需要另设数组记录偏移量。但当一个集合中有多种关系时(状态不止一种)就需要用种类并查集了。

具体实现如下:

对于集合里的任意两个元素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,若一致则为真,否则为假。

以图示表示为:

 

一般化总结:

并查集的偏移向量属于并查集的变形,只要适用于集合数目较少,或是固定的并查集类型。
比如1182中的食物链只有3个集合,(同类、天敌、食物)。
2492中的只有两个集合。(同类,异类)。
1703中的只有两个帮派。(龙帮、蛇帮)。
在处理此类集合绝对数量少但集合之间关系复杂的并查集,比如(1182,A吃B,B吃C,C吃D,那么A.D是同类。)

状态存储,只需要在新加一个记录偏移量的数组offset [ MAX ] ,如果只有两个状态,可以直接开bool数据类型。
在预处理的时候,加上如下
void makeset ( )
{
        int i ;
        for ( i = 1 ; i <= n ; ++ i )
        {
                father [ i ] = i ;
                offset[ i ] = 0 ;//新加的
        }
}

在查集函数中,
int findset ( int x )
{
        int t ; 
        if ( father [ x ] == x ) return x ;
        else t = findset( father [ x ] ) ;
        offset [ x ] = ( offset [ x ] + offset [ father [ x ] ] ) % DEPTH ;//DEPTH表示几个状态量
//如果1182中,DEPTH=3;
        father [ x ] = t ;
        return t ;
}//使用函数递归调用查找父亲在处理上优于循环。
在并集函数中,
void unionset ( int x , int y )
{
        int fx , fy ;
        fx = findset ( x ) ;
        fy = findset ( y ) ;
        if ( fx == fy ) return ;
        father [ fx ] = fy ;
        offset [ fx ] = ( offset [ y ] - offset [ x ] + d +DEPTH ) % DEPTH ;
}//d表示x与y的状态偏移量,如1182中,偏移量可能是食物1,或是天敌2;1703中只有反面就是1。
offset [ x ] = ( offset [ x ] + offset [ father [ x ] ] ) % DEPTH ;
offset [ fx ] = ( offset [ y ] - offset [ x ] + d +DEPTH ) % DEPTH ;
在查集函数中,向量转移方程表示儿子与祖先的偏移量=(儿子与父亲的偏移量+父亲和祖先的偏移量)%状态量。(这里的父亲和祖先可能是同一人,但没有关系,因为自己和自己的偏移量为0。)
在并集函数中,由于要将两个原本不相干的集合合并,d表示在两个集合交汇的地方的最深层的偏移量。
offset[y]表示y与y祖先的偏移量,offset[x]便是x与x祖先的偏移量。
加上一个DEPTH是为了防止出现负数,用%会得到意外的结果。

 

数据结构:种类并查集


#include<stdio.h>int father[50005],depth[50005];int delta[50005],n;void init(int n){    int i;    for(i = 1;i <= n;i ++)    {        father[i] = i;        depth[i] = 0;        delta[i] = 0;    }    return ;}int find(int x){    if(x == father[x])      return x;    int temp = find(father[x]);    delta[x] = (delta[x]+delta[father[x]])%3;    return father[x] = temp;}void unit(int x,int y,int d){    x = find(x);    y = find(y);    if(x == y)      return ;    if(depth[x] > depth[y])    {        father[y] = x;        delta[y] = (delta[x]-delta[y]+d+3)%3;    }    else    {        if(depth[x] < depth[y])        {            father[x] = y;            delta[x] = (delta[y]-delta[x]+d+3)%3;        }        else        {            father[x] = y;            delta[x] = (delta[y]-delta[x]+d+3)%3;            depth[y]++;        }    }}int judge(int x,int y,int d){    if(x > n || y > n)      return 1;    if(find(x) != find(y))    {        unit(x,y,d);        return 0;    }    if(d-1 == (delta[y]-delta[x]+3)%3)        return 0;    return 1;}int main(){    int k,cnt;    int d,x,y;    freopen("in.c","r",stdin);    while(~scanf("%d%d",&n,&k))    {        cnt = 0;        init(n);        while(k--)        {            scanf("%d%d%d",&d,&x,&y);            if(judge(x,y,d))              cnt++;        }        printf("%d\n",cnt);    }    return 0;}


 

 

0 0
原创粉丝点击