POJ-1182 食物链

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

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

储存关系——

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

抽象问题——

已知x与root_x的关系,y与rt_y的关系,给出x与y的关系,求rt_y与root_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

公式出来了:(relation记录结点和祖先之间的关系)
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的特殊性,两个式子可以合并。但是我并不赞成这么做。

可行性——

上面提到了路径压缩会改变父亲,那么并查集还可行吗?当然可行了,只是需要换一个姿势。递归的时候,多记录一点信息,就可以保证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]]
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;


现在剩下最后一个问题。

逻辑判断——

如何判断当前的话和之前的真话冲突呢?这就是逻辑关系的问题了。
1. 若A、B不在统一的集合中——(且没有出现第2第3类错误)这是真话,应当执行“并”操作。否则——
2. 若A和B是同类,那么A与集合祖先的关系和B与集合祖先的关系应该相同
3. 若A吃B,A、B与集合祖先的关系是已知的,很容易判断A是否吃B。
A->B A->root B->root
0 1
1 2
2 0

即(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];
		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;
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值