poj 1182 食物链 && nyoj 207(种类并查集)

食物链
Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 52414   Accepted: 15346

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

题意为:有A、B、C三种动物。A吃B,B吃C,C吃A,并给出一些条件,推断为你提供信息的人说了多少句假话。
并查集。通经常使用来检查两个元素是否在同一集合中,或者是将两个不同的集合为一个集合。
至于这道题。有神人曰。利用并查集,同一时候对每一个节点保持其到根结点的相对类别偏移量,定义为:
               0——同类;
               1——食物;
               2——天敌。

一个非常具体的解题报告,看得我五体投地:点击打开链接

这里他讲的异常具体,哪一点都有讲到,,当中60行到180行就有下边凝视的公式的解析

好厉害。,2015,7,27

#include<stdio.h>
#define M 50005
int x[M],re[M];
void init()
{
	for(int i=0;i<M;i++){
		x[i]=i; re[i]=0;
	}
}
int find(int k)
{
	int temp=x[k];
	if(x[k]==k) return k;
	x[k]=find(x[k]);
	re[k]=(re[k]+re[temp])%3;//( 儿子的关系 + 父亲的关系 ) % 3 = 儿子对爷爷的关系
	return x[k];
}
void merge(int a,int b,int fa,int fb,int d)
{
	x[fa]=fb;
	re[fa]=(re[b]-re[a]+d+3)%3;//d是a与b的关系,(3-re[a])是a为根节点时,他父亲的关系,(re[b]-re[a]+d+3)%3就是a的根节点与他的父亲就是b的根节点的关系 
}
int main()
{
	int n,m,a,b,d,fa,fb,count=0;
	scanf("%d%d",&n,&m);
	init();
	while(m--){
		scanf("%d%d%d",&d,&a,&b);
		if( a>n || b>n || (a==b && d==2) ){
			count++;
			continue;
		}
		fa=find(a);
		fb=find(b);
		if(fa==fb&&(re[a]-re[b]+3)%3!=d-1)//3-re[b]就得到了根节点和b的关系,re[a]+3-re[b]就是a关于b的关系  
			count++;
		else
			merge(a,b,fa,fb,d-1);
	}
	printf("%d
",count);
	return 0;
}
还有第二种比較巧妙的简单的方法:
对于每仅仅动物i创建3个元素i-A, i-B, i-C, 并用这3*N个元素建立并查集。这个并查集维护例如以下信息:
① i-x 表示 “i属于种类x”。


②并查集里的每个组表示组内全部元素代表的情况都同一时候发生或不发生。


比如,假设i-A和j-B在同一个组里,就表示假设i属于种类A那么j一定属于种类B,假设j属于种类B那么i一定属于种类A。


因此,对于每一条信息。仅仅须要依照以下进行操作就能够了。
1)第一种。x和y属于同一种类———合并x-A和y-A、x-B和y-B、x-C和y-C。
2)另外一种,x吃y—————————合并x-A和y-B、x-B和y-C、x-C和y-A。
只是在合并之前须要先推断合并是否会产生矛盾。比如在第一种信息的情况下,
须要检查比方x-A和y-B或者y-C是否在同一组等信息。


(一開始我一直不明确。对于两种信息都是合并,
那么以后怎么分清究竟是同类还是捕食关系呢,或者说怎样推断是否会产生矛盾呢?
后来发现,它利用3*N的数组分3段1~N,N~2N,2N~3N分别当做是A、B、C三个种类的集合,
把全部可能符合的情况都会导入进去。尽管对于两种信息的操作都是合并。
但合并的内容是不一样的,这样就能够在合并之前推断其是否以还有一种信息合并过或者符合还有一种信息。能够自己举例来理解一下)

#include<stdio.h>
#define M 50005*3
int x[M];
void init()
{
	for(int i=0;i<M;++i){
		x[i]=i;
	}
}
int find(int k)
{
	if(x[k]==k) return k;
	x[k]=find(x[k]);
	return x[k];
}
void merge(int a,int b)
{
	int fa=find(a); int fb=find(b);
	if(fa!=fb) x[fa]=fb;
}
bool same(int a,int b)
{
	return find(a)==find(b);
}
int main()
{
	int n,m,a,b,c,count=0;
	scanf("%d%d",&n,&m);
	init();
	while(m--){
		scanf("%d%d%d",&c,&a,&b);
		//元素a。a+N,a+2*N分别代表a-A。a-B,a-C 
		if(a>n || b>n ||(a==b && c==2)) {
			count++;
			continue;
		}
		if(c==1){
			if(same(a,b+n) || same(a,b+2*n)) 
				count++;
		//对于第一种信息是不能出现捕食与被捕食关系的  
			else{	
				merge(a,b);
				merge(a+n,b+n);
				merge(a+2*n,b+2*n);
			}
		}
		else{
			if(same(a,b) || same(a,b+2*n)) count++;
			//不能出现捕食同类和反捕食的情况 
			else{
				merge(a,b+n);
				merge(a+n,b+2*n);
				merge(a+2*n,b);
			}
		}
	}
	printf("%d
",count);
	return 0;
 } 


原文地址:https://www.cnblogs.com/mthoutai/p/6936419.html