食物链
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 Source
Noi 01
|
————————————————————午休的分割线————————————————————
前言:学习(关系)并查集不能不学的经典例题,在网上搜到很多一个比一个详细的题解。当然我自己的题解也相当费心了,转载请注明——出处:http://blog.csdn.net/j_sure/article/details/25884819
推荐三个网址:match7 Cfreezhan c0de4fun
思路:之前学习并查集的模板(参见:畅通工程),使用了路径压缩。但是对路径压缩的理解模模糊糊。这道题就是让你深刻理解路径压缩究竟是怎么一回事的。
看到很多人的题解报告一开始就说,设0、1、2分别对应着一种结点与其父亲之间的关系,然后给出了推导公式的过程。我个人觉得没有从问题的本质开始分析,难以看懂。让我们一起来分析一下。
首先,并查集是一种擅长处理集合之间合并与查询的树型数据结构。集合之间的合并,就意味着事物之间关系的改变。相当抽象也非常有趣。例如此题,ABC是三种动物,相互有着“吃”或“被吃”的关系,之后给出庞大数量的三种动物,找到这些动物之间符合逻辑的关系。
动机——
每当阐明一个动物与另一个动物之间的关系,即可先进行逻辑判断,之后根据需要来储存它们的“关系信息”并且纳入集合当中。
建立这种树型数据结构的时候,如果这棵树深度越来越大,甚至变成一条长链,那么查询耗费的时间是相当无法忍受的。因此我们在查询的同时,进行递归式的“路径压缩”。只有路径压缩才能让查询复杂度变成O(1)。
int Find(int x) {
if(x != fath[x]) fath[x] = Find(fath[x]);
return fath[x];
}
上面的路径压缩,递归的边界是x的父亲是自己(祖先)。这是灵活的,因为我们在初始化的时候,每个结点的父亲都是自己,这时候每个结点“自成一个集合”。大概思考之后,发现每次查询都是查到祖先为止,之后返回祖先。但是仔细地思考一下,姑且假设存在一条长链,那么递归到边界(即查到祖先)之后,回溯的过程显然是在“改变父亲”。也就是说,每回溯一层,就会使该结点的父亲变成祖先。(这是在“压缩父亲”)
理解了路径压缩,自然而然会想到一个问题。并查集通过建立父子关系来储存数据,那么想要储存捕食关系也只能储存结点与其父亲之间的捕食关系,那么既然会改变父亲,而每个结点储存的与其父亲之间的捕食关系势必发生变化,那么路径压缩真的可行吗?暂时不考虑这个,先考虑如何通过输入储存结点信息。
储存关系——
抽象问题——
A -> B | A -> rt_A | B -> rt_B | rt_B -> rt_A | A -> B | A -> rt_A | B -> rt_B | rt_B -> rt_A | |
同类 | 0 | 0 | 0 | 吃 | 0 | 0 | 1 | |
同类 | 0 | 1 | 2 | 吃 | 0 | 1 | 0 | |
同类 | 0 | 2 | 1 | 吃 | 0 | 2 | 2 | |
同类 | 1 | 0 | 1 | 吃 | 1 | 0 | 2 | |
同类 | 1 | 1 | 0 | 吃 | 1 | 1 | 1 | |
同类 | 1 | 2 | 2 | 吃 | 1 | 2 | 0 | |
同类 | 2 | 0 | 2 | 吃 | 2 | 0 | 0 | |
同类 | 2 | 1 | 1 | 吃 | 2 | 1 | 2 | |
同类 | 2 | 2 | 0 | 吃 | 2 | 2 | 1 |
if(def == 1)
tree[rt_y].relation = (tree[x].relation - tree[y].relation + 3) % 3;
else
tree[rt_y].relation = (tree[x].relation - tree[y].relation + 1 + 3) % 3;
根据def的特殊性,两个式子可以合并。但是我并不赞成这么做。
可行性——
int Find(int x)
{
int tmp;
if(x != tree[x].parent) {
tmp = tree[x].parent;//暂存父亲
tree[x].parent = Find(tmp);//压缩父亲
tree[x].relation = ......;//修改关系
}
return tree[x].parent;
}
所修改的关系即——通过x和它父亲(tmp)的关系以及tmp和tmp的父亲的关系推出x和它爷爷(tmp的父亲)的关系,将x的父亲(tmp)压缩,修改成x的爷爷。(这么一来就压缩掉了一个父亲,同时修改了关系)推导的过程依然是找规律。
x -> fa[x] | fa[x] -> fa[fa[x]] | x -> fa[fa[x]] |
0 | 0 | 0 |
0 | 1 | 1 |
0 | 2 | 2 |
1 | 0 | 1 |
1 | 1 | 2 |
1 | 2 | 0 |
2 | 0 | 2 |
2 | 1 | 0 |
2 | 2 | 1 |
//压缩掉tmp之后,应修改tree[x].relation为x与爷爷的关系
tree[x].relation = (tree[x].relation + tree[tmp].relation) % 3;
逻辑判断——
A->B | A->root | B->root |
吃 | 0 | 1 |
吃 | 1 | 2 |
吃 | 2 | 0 |
新姿势——
/*我的题解报告:http://blog.csdn.net/j_sure/article/details/25884819*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <string>
#include <iostream>
using namespace std;
/****************************************/
const int N = 50010;
const int r_sam[3][3] = {0, 2, 1, 1, 0, 2, 2, 1, 0}, r_eat[3][3] = {1, 0, 2, 2, 1, 0, 0, 2, 1};
const int r_gra[3][3] = {0, 1, 2, 1, 2, 0, 2, 0, 1};
int ans;
struct Tree
{
int parent;
char relation;
}tree[N];
int Find(int x)
{
int tmp;
if(x != tree[x].parent) {
tmp = tree[x].parent;
tree[x].parent = Find(tmp);
tree[x].relation = r_gra[tree[x].relation][tree[tmp].relation];
}
return tree[x].parent;
}
void Union(int def, int x, int y)
{
int rt_x = Find(x), rt_y = Find(y);
if(rt_x != rt_y) {
tree[rt_y].parent = rt_x;
if(def == 1)
tree[rt_y].relation = r_sam[tree[x].relation][tree[y].relation];
else
tree[rt_y].relation = r_eat[tree[x].relation][tree[y].relation];
return ;
}
if(def == 1 && tree[x].relation != tree[y].relation) {
ans++;
return ;
}
if(def == 2 && (tree[x].relation + 1) % 3 != tree[y].relation) {
ans++;
return ;
}
}
int main()
{
int n, k;
scanf("%d%d", &n, &k);
ans = 0;
for(int i = 1; i <= n; i++) {
tree[i].parent = i;
tree[i].relation = 0;
}
for(int i = 1; i <= k; i++) {
int def, x, y;
scanf("%d%d%d", &def, &x, &y);
if(x > n || y > n) {
ans++;
continue;
}
if(def == 2 && x == y) {
ans++;
continue;
}
Union(def, x, y);
}
printf("%d\n", ans);
return 0;
}