【问题描述】
动物王国中有三类动物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);
else
if((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;
}