食物链

食物链

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 7 1 101 1 2 1 2 2 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是为了防止出现负数,用%会得到意外的结果。

数据结构:种类并查集

AC代码:

 1 #include<stdio.h>
 2 int father[50005],depth[50005];
 3 int delta[50005],n;
 4 void init(int n)
 5 {
 6     int i;
 7     for(i = 1;i <= n;i ++)
 8     {
 9         father[i] = i;
10         depth[i] = 0;
11         delta[i] = 0;
12     }
13     return ;
14 }
15 
16 int find(int x)
17 {
18     if(x == father[x])
19       return x;
20     int temp = find(father[x]);
21     delta[x] = (delta[x]+delta[father[x]])%3;
22     return father[x] = temp;
23 }
24 
25 void unit(int x,int y,int d)
26 {
27     x = find(x);
28     y = find(y);
29     if(x == y)
30       return ;
31     if(depth[x] > depth[y])
32     {
33         father[y] = x;
34         delta[y] = (delta[x]-delta[y]+d+3)%3;
35     }
36     else
37     {
38         if(depth[x] < depth[y])
39         {
40             father[x] = y;
41             delta[x] = (delta[y]-delta[x]+d+3)%3;
42         }
43         else
44         {
45             father[x] = y;
46             delta[x] = (delta[y]-delta[x]+d+3)%3;
47             depth[y]++;
48         }
49     }
50 }
51 
52 int judge(int x,int y,int d)
53 {
54     if(x > n || y > n)
55       return 1;
56     if(find(x) != find(y))
57     {
58         unit(x,y,d);
59         return 0;
60     }
61     if(d-1 == (delta[y]-delta[x]+3)%3)
62         return 0;
63     return 1;
64 }
65 
66 int main()
67 {
68     int k,cnt;
69     int d,x,y;
70     freopen("in.c","r",stdin);
71     while(~scanf("%d%d",&n,&k))
72     {
73         cnt = 0;
74         init(n);
75         while(k--)
76         {
77             scanf("%d%d%d",&d,&x,&y);
78             if(judge(x,y,d))
79               cnt++;
80         }
81         printf("%d
",cnt);
82     }
83     return 0;
84 }
原文地址:https://www.cnblogs.com/anhuizhiye/p/3384874.html