题目链接:http://poj.org/problem?id=1182
题目解释:
描述
动物王国中有三类动物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),输出假话的总数。
输入
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
输出
只有一个整数,表示假话的数目。
题目分析:
这题的核心在于设置一个dis的数组 他的值为 当前节点和他的父节点的关系
0表示和他父节点同类
1表示被父节点吃
2表示吃父节点
至于为什么这么表示 其实交换顺序大同小异但是状态只有三个 所以取膜后表示就只用012来表示了
只是用上述表示方法这样做起来省点代码量
那么整个问题就只要处理俩个方面的内容 一个是 路径的压缩 一个是树的合并
对于树的合并 因为find(a) find(b) 已经做了路径压缩 那么a b俩颗树就只剩下高度为二的树了
接着 对于 x->rx y->ry 要让ry->rx 那么此时需要更新dis[ry]的值 怎么更新呢
于是需要找规律了 ,对于dis[x] dis[y] 已知的话 dis[ry] 就自然出来了(当然 让rx指向ry也是可以的)
那么 我们又3*3 9种情况 下面一一列举
dis[x] dis[y] 过程分析 求出来dis[dy]
0 0 x与rx同类,y与ry同类 x吃y 所以rx吃ry 被父吃 1
0 1 x与rx同类 y被ry吃 x吃y 所以rx与ry同类 0
0 2 x与rx同类 y吃ry x吃y 所以ry吃rx 吃父 2
1 0 rx吃x y与ry同类 x吃y 所以ry吃rx 吃父 2
1 1
1 2 (同理可推)
2 0
2 1
2 2
所以归纳下 就是 先求个差值 (dis[x]-dis[y]+3)%3 ==dis[dy]-1
所以可以得到这个总结式 dis[dy] = (dis[x]-dis[y]+3+1)%3
注意的是 以上的操作是在x吃y的状态下得到的结论
那么当x与y是同类的话 那怎么合并俩颗树呢
想法一样,同样的去列表出来,虽然做起来麻烦,但是做多了也就熟练这种类似的方法,以后再碰到类似的
做法,就可以很快的写出结论.
那么现在要解决的是第二个问题
那就是怎么进行路径压缩
在这个问题上面 我们就是要处理find(x)这个函数
目的就是要把x这颗树压缩成为俩层的树
那怎么再压缩的同时修改dis数组呢
我们不妨用数学归纳法证明一下
当只有俩层的时候就不需要动了 dis数组不改
假如有三层的时候 例如x(儿子)----->px(父亲)------->rx(爷爷)
现在就是要把儿子直接指向爷爷 而父亲和爷爷都不要改动
那么怎么修改呢 ? 此时我们又需要找规律打表了
dis[old x] dis[px] 过程描述 dis[new x]
0 0 x与px同类 px与rx同类 所以x与rx同类 0
0 1 x与px同类 rx吃px 所以rx吃x 1
0 2 x与px同类 px吃rx 所以x吃rx 2
1 0
1 1 (同理可推)
1 2
2 0
2 1
2 2
以下也可以如此推理得到
那么我们现在就很容易发现三者的规律 颗得到式子
dis[new x] = (dis[old x]+dis[px])%3
那么由数学归纳法知道 如果有四层 那么我们先更新前三层压缩成俩层 加上最先面那层又变成了三层 相当于处理两次
那么我们就用递归处理第i层 思想是递归到根节点 然后依次更新下来 最后更新当前i节点
到此我们就完成了90%了。
#include<iostream>
#include<cstring>
using namespace std;
#define maxn 50005
#define scanf(a) scanf("%d",&a)
#define print(a) printf("%d\n",a)
#define mem(a) memset(a,0,sizeof(a))
#define init(a) for(int i=0;i<maxn;i++) a[i] =i
#define Max(a,b) a>b?a:b
#define Min(a,b) a<b?a:b
int pre[maxn],dis[maxn];
int find(int x)
{
if(x!=pre[x])
{
int px=pre[x];
pre[x] = find(pre[x]);
dis[x] = (dis[x]+dis[px])%3;
}
return pre[x];
}
void add(int x,int y,int relation)
{
int rx = find(x);
int ry = find(y);
if(rx!=ry)//已经压缩成了高度为二的树 现在需要找规律
{
pre[ry] = rx;//x吃y dis[x] = dis[y]+2
dis[ry] = (dis[x]-dis[y]+3+relation-1)%3; //9*9的情况找规律
}
// find(x);
}
int main()
{
int n,k,ans=0;
scanf(n);scanf(k);
init(pre);mem(dis);
while(k--)
{
int c,x,y;
scanf(c);scanf(x);scanf(y);
if(x>n||y>n||(c==2&&x==y))
{
ans++;
continue;
}
//
int rx = find(x);
int ry = find(y);
if(rx == ry)//是同棵树 判断
{
if(c==1&&(dis[x]%3-dis[y]%3+3)%3!=0)
{
ans++;
}
if(c==2&&(dis[x]%3-dis[y]%3+3)%3!=2)
{
// cout<<"!";
ans++;
}
}
else
add(x,y,c);
}
printf("%d\n",ans);
}