食物链

来源:互联网 发布:新闻联播视频制作软件 编辑:程序博客网 时间:2024/06/05 13:25
        动物王国中有三类动物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

题意:n个动物 k句话 有一种循环a吃b 吃c c吃a 开始不知道n种动物关系是什么,两种询问:d=1 x y为同类 d=2 x吃y 判断假话的条数(关键之违背之前的关系)

题解:

我们首先用动物之间“相对”的关系flag[x]来确定一个并查集(flag[x]表示set[x]与x的关系)

0 - 这个节点与它的父节点是同类:flag[x] == 0 表示set[x]与x是同类
1 - 这个节点被它的父节点吃 ,父吃子:flag[x] == 1 表示x吃set[x]
2 - 这个节点吃它的父节点,子吃父:flag[x] == 2 表示set[x]吃x

一.位移量:

我们注意到,

当 d = 1的时候,( d - 1 ) = 0, 则 x 和 y 是同类 ,那么 y 对 x 的关系是 0
当 d = 2的时候,( d - 1 ) = 1,则 x 吃了 y, 那么 y 对 x 的关系是 1, x 对 y 的关系是 2.


综上所述 ,无论 d为1 或者是为 2, y 对 x 的关系都是 d-1,故a->b的偏移值为d-1,(d - 1) 这是x和y之间的flag

二.路径压缩时的节点算法:

优化压缩时节点的变化,新的flag=(当前flag+父节点flag)%3(由穷举法可得%3,%3保证了偏移量取值始终在[0,2]间)

三.集合间的关系:

穷举法:父相对子的flag,即x是y的父节点时:
0 (3 - 0 ) % 3 = 0
1(父吃子)   ( 3 - 1 ) % 3 = 2 //父吃子
2(子吃父)      ( 3 - 2 ) % 3 = 1 //子吃父

x是y的父节点时,y的flag就是这个 3 - flag[y]


综上:y所在集合的根结点与x所在集合的根节点的关系:flag[find(y)]=(3-flag[y]+(d-1)+flag[x]) % 3;(x是y的父节点)

三.算法正确性:

1.如果在同一棵树中find(x) == find(y):直接判断是否在说谎。


1)如果 d ==1,那么 x 与 y 应该是同类,他们的r[]应该相等,如果不相等,则说谎数 +1

(详解)若两根节点在同一集合flagb=flaga

   若两根节点不在同一集合设2到5为x,因为4与6为同类,如图,所以4到6为0,所以0+x=0+2;同理 3与6为同类1+x=0+2;1与6为同类 1+x=0+2

故flaga+x=0+flagb,所以x=flagb-flaga=(flagb-flaga+3)%3  (+3防止flagb-flaga小于0)

2)如果 d==2,那么 x 应该吃了 y,也就是 (r[x]+1)%3 == r[y]如果不满足,则说谎数 +1


(详解)若两根节点不在同一集合,合并两不同根结点。如果 4吃6为0+x=1+2;同理3吃6为1+x=1+2,1吃0为2+x=1+0;
故x=1+flagb-flaga=(1+flagb-flaga+3)%3 (+3防止flagb-flaga+1小于0)
若两根节点在同一集合,推出 flaga=(1+flagb)%3 a能吃b否则a不能吃b

2.如果不在同一棵树,则合并 x 与 y 分别所在的树

#include<iostream>  #include<algorithm>  #define N 50005  using namespace std;  int set[N];  //flag[x]表示set[x]与x的关系  int flag[N];  void Init_set(int n) {      for(int i=0; i<n; i++) {          set[i]=i;          flag[0]=0;      }  }  int find(int x) {      if(set[x]==x)          return set[x];      //find(x)不仅找x所在集合的根,而且更新x中根的所有关系      int temp=set[x];      set[x]=find(set[x]);      /*     优化压缩时节点改变,更新set[x]与x的关系,flag[]%3==同类0||父x吃子set[x]:1||子吃父:2*/      flag[x]=(flag[x]+flag[temp])%3;      return set[x];  }  void merge(int a,int b,int d) {      int aa=find(a);      int bb=find(b);      //将集合aa合并到集合bb上,即aa是bb的父节点      set[bb]=aa;      //更新set[a]与a的关系      flag[bb]=(flag[a]+3-flag[b]+(d-1))%3;  }  int main() {      int n;      int m;      int ans=0;      //用cin会超时不知为甚       scanf("%d%d", &n, &m);      Init_set(n);      while(m--) {          int d;          int x;          int y;          scanf("%d%d%d", &d, &x, &y);          if(x>n||y>n||(x==y&&d==2))              ans++;          //在同一个集合中          else if(find(x)==find(y)) {              //同类              if(d==1&&flag[x]!=flag[y])                  ans++;              //捕食              if(d==2&&(flag[x]+1)%3!=flag[y])                  ans++;          } else              merge(x,y,d);      }      cout<<ans<<endl;      return 0;  } 


原创粉丝点击