POJ-1182 食物链

来源:互联网 发布:摇钱树网吧计费软件 编辑:程序博客网 时间:2024/05/22 06:11
食物链
Time Limit: 1000MS Memory Limit: 10000K   

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 71 101 1 2 1 22 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的父亲是自己(祖先)。这是灵活的,因为我们在初始化的时候,每个结点的父亲都是自己,这时候每个结点“自成一个集合”。大概思考之后,发现每次查询都是查到祖先为止,之后返回祖先。但是仔细地思考一下,姑且假设存在一条长链,那么递归到边界(即查到祖先)之后,回溯的过程显然是在“改变父亲”。也就是说,每回溯一层,就会使该结点的父亲变成祖先。(这是在“压缩父亲”)

理解了路径压缩,自然而然会想到一个问题。并查集通过建立父子关系来储存数据,那么想要储存捕食关系也只能储存结点与其父亲之间的捕食关系,那么既然会改变父亲,而每个结点储存的与其父亲之间的捕食关系势必发生变化,那么路径压缩真的可行吗?暂时不考虑这个,先考虑如何通过输入储存结点信息。

储存关系——

如果说每当阐明两个动物之间的关系,我就要储存这个信息并且将这两个动物纳入集合之中,我该怎样储存这个信息呢?我们知道“路径压缩”使得并查集这个结构当中每个结点的父亲都是它的祖先,那么自然,储存的信息是“该结点与其祖先之间的关系”。问题在于,阐明的是两个动物之间的关系,怎样通过两个动物之间的关系,将它们同时纳入集合,并且储存各自与集合根节点的关系呢。(使集合的根节点成为他们的祖先)
方法就是调用 x 与其祖先的关系、y 与其祖先的关系,然后通过 x 和 y 的关系推导出 y 的祖先与 x 的祖先的关系。并且自始至终,我们把 x 的祖先设为集合根节点,每次都将 y 变成 x 祖先的子孙。

抽象问题——

已知x与root_x的关系,y与rt_y的关系,给出x与y的关系,求rt_y与root_x的关系。没别的方法。枚举归纳吧。
A -> BA -> rt_AB -> rt_Brt_B -> rt_A A -> BA -> rt_AB -> rt_Brt_B -> rt_A同类000 吃001同类012 吃010同类021 吃022同类101 吃102同类110 吃111同类122 吃120同类202 吃200同类211 吃212同类220 吃221
公式出来了:(relation记录结点和祖先之间的关系)
if(def == 1)tree[rt_y].relation = (tree[x].relation - tree[y].relation + 3) % 3;elsetree[rt_y].relation = (tree[x].relation - tree[y].relation + 1 + 3) % 3;
根据def的特殊性,两个式子可以合并。但是我并不赞成这么做。

可行性——

上面提到了路径压缩会改变父亲,那么并查集还可行吗?当然可行了,只是需要换一个姿势。递归的时候,多记录一点信息,就可以保证relation储存的信息的正确性了。这是在学习DFS、记忆化搜索的时候的经验。
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]]000011022101112120202210221
//压缩掉tmp之后,应修改tree[x].relation为x与爷爷的关系tree[x].relation = (tree[x].relation + tree[tmp].relation) % 3;


现在剩下最后一个问题。

逻辑判断——

如何判断当前的话和之前的真话冲突呢?这就是逻辑关系的问题了。
1. 若A、B不在统一的集合中——(且没有出现第2第3类错误)这是真话,应当执行“并”操作。否则——
2. 若A和B是同类,那么A与集合祖先的关系和B与集合祖先的关系应该相同
3. 若A吃B,A、B与集合祖先的关系是已知的,很容易判断A是否吃B。
A->BA->rootB->root011220
即(A.rela + 1) % 3 == B.rela
到此,这道题就解决了。初始化的时候,自己和自己应该是同类。另外POJ上这道题只允许单组数据格式。

新姿势——

跟着Roll神学习了新姿势。并不需要什么公式,既然是枚举,打个表就行。开几个[3][3]的数组。类似映射。将关系哈希出来。
代码如下:
/*我的题解报告: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];elsetree[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;}


0 0
原创粉丝点击