食物链

来源:互联网 发布:四川省审计厅数据采集 编辑:程序博客网 时间:2024/03/29 16:16

【问题描述】

动物王国中有三类动物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),输出假话的总数。 

输入文件(eat.in)

第一行是两个整数N和K,以一个空格分隔。

以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。

  若D=1,则表示X和Y是同类。

  若D=2,则表示X吃Y。 

输出文件(eat.out)

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

输入样例

输入文件

对7句话的分析

100 7

 

1 101 1  

假话

2 1 2     

真话

2 2 3    

真话

2 3 3    

假话

1 1 3    

假话

2 3 1    

真话

1 5 5    

真话

 输出样例

3

数据和题解 密码:a5tk

【问题分析】

食物链应该是并查集的开山之作,虽然经过广大的oier’s的努力,难度已经从国际级变成了基础级,但是对新手来说此题的处理和理解上还是有很多难点,那为了节约大家的时间,我就总结下大家对此题的理解,做一个史上最详尽的食物链的题解,保证让让傻瓜也能看懂。

【权值(relation)的确定】

我们根据题意,森林中有3种动物。A吃B,B吃C,C吃A。我们还要使用并查集,那么,我们就以动物之间的关系来作为并查集每个节点的权值。
       注意,我们不知道所给的动物(题目说了,输入只给编号)所属的种类。所以,我们可以用动物之间“相对”的关系来确定一个并查集。
  0 - 这个节点与它的父节点是同类
  1 - 这个节点被它的父节点吃
  2 - 这个节点吃它的父节点。
       注意,这个0,1,2所代表的意义不是随便制定的,我们看题目中的要求。说话的时候,第一个数字(下文中,设为d)指定了后面两种动物的关系:
  1 - X与Y同类
  2 - X吃Y
    我们注意到,当 d = 1的时候,( d - 1 ) = 0,也就是我们制定的意义,当 d = 2的时候,( d - 1 ) = 1,代表Y被X吃,也是我们指定的意义。所以,这个0,1,2不是随便选的

【 路径压缩,以及节点间关系确定】

确定了权值之后,我们要确定有关的操作。我们把所有的动物全初始化 relation[i]=0,f[i]=i
(1)路径压缩时的节点算法,路径压缩的关键是把路径上的节点都指向根节点后,那相对关系也发生了变化,那如何修改节点和根节点的关系呢通过穷举我们可以发现,当前节点与祖父节点的关系可以得出如下公式。
relation[now]=(relation[now]+relation[f[now]]) % 3
这个路径压缩算法是正确的
关于这个路径压缩算法,还有一点需要注意的地方,我们一会再谈注意,根据当前节点的relation和当前节点父节点的relation推出当前节点与其父节点的父节点的relation这个公式十分重要!!它推不出来下面都理解不了!!自己用穷举法推一下:
好吧,为了方便伸手党,我给出穷举过程

          

       当x,y不在一个集合时就需要合并操作,合并时除了直接把x,y的根root(x)和root(y)并起来外,还需要重新计算root(x)和root(y)之间的关系,如果是把root(y)挂在root(x)上,那么可以推出公式:
      relation[root(y)]=(3-relation[y]+(d-1)+relation[x]) % 3;
这个公式,是分三部分,这么推出来的:
( d - 1 ) :这是X和Y之间的relation,X是Y的父节点时,Y的relation就是这个
3 - relation[y] = 根据Y与根节点的关系,逆推根节点与Y的关系       
这部分也是穷举法推出来的,我们举例:
    0(父子同类)( 3 - 0 ) % 3 = 0
   1(父吃子) ( 3 - 1 ) % 3 = 2 //父吃子
   2(子吃父) ( 3 - 2 ) % 3 = 1 //子吃父,一样的哦亲
注意,这个当所有集合都是初始化状态的时候也适用


【判断】

先处理特殊情况:
       1.当x>n或y>n时,为假话
  2.当d=2而x=y时,为假话
(1)首先,如何判断1 X Y是不是假话。//此时 d = 1
   if ( X 和 Y 不在同一集合) Union(x,y,xroot,yroot,d)   
   else if x.relation != y.relation ->假话
(2)其次,如何判断2 X Y是不是假话 //此时d = 2
   if ( X 和 Y 不在同一集合)Union(x,y,xroot,yroot,d)
   else (relation [y]+ 3 - relation[x] ) % 3 != 1 ->假话
  这个公式是这么来的:
  3 - relation[x]得到了根节点关于x的relation,
  relation [y]+ 3 - relation[x]得到了y关于x的relation,所以,只要y关于x的relation不是1,就是y不被x吃的话,这句话肯定是假话!
    综合(1) 和(2),无论d=1或2,只要满足 ((relation[y]-relation[x]+3) % 3)<>(d-1) 即为假话,3要加上,不然可能出现负数。

#include<iostream>#include<cstring>#include<cstdio>using namespace std;int relation[50010],f[50010];int n,k,sum=0;void init();void merge(int,int,int,int,int);int find(int);void work();int main(){init();work();return 0;}void init(){memset(relation,0,sizeof(relation));cin>>n>>k;for(int i=1;i<=n;i++) f[i]=i;}void work(){int d,x,y;for(int i=1;i<=k;i++){cin>>d>>x>>y;if(x>n||y>n){sum++;continue;}if(n==2 && x==y){sum++;continue;}int fx,fy;fx=find(x);fy=find(y);if(fx!=fy) merge(fx,fy,x,y,d);elseif((relation[y]+3-relation[x])%3!=(d-1)) sum++;}cout<<sum<<endl;}int find(int x){if(f[x]==x) return x;int fx;fx=find(f[x]);//不能直接f[x]=find(f[x]),更新f[x]后就无法计算x和根的关系了;relation[x]=(relation[x]+relation[f[x]])%3;f[x]=fx;//路径压缩return f[x]; }void merge(int fx,int fy,int x,int y,int d){f[fy]=fx;relation[fy]=(3-relation[y]+d-1+relation[x])%3;}


0 0
原创粉丝点击