poj.1182

          这道题是并查集的典型题目,题目很有意思,就是根据给出的k句话,和评判标准,判断假话的个数。这道题的基本分析应该是这样的,根据已经说的真话来建立各动物之间的关系,由建立的关系来判断后续话的真假。这里,我们将A吃B记作set[A]=(set[B]+1)%3,同时set[B]=(set[A]+2)%3,还剩一种情况,若A与B是同类,则set[A]=set]B],(也就是说,他们之间的关系可以用上述表达式来表示),相应的我们为每种动物设置一个标号set(其值为0,1,2),单独没有任何意义,只有和其他动物的标号组合起来才有意义。)这样若两个动物还没有确定关系,则可以通过上述表达式建立关系,若已经有了关系,则可以通过上述表达式来判断关系是否正确。
          注意这是核心思想,但实现的方式有多种,这里,我开始写了一种并非基于并查集的数组模拟法。该方法的思想是首先判断给出的话中的两个动物是否已经出现,这里分四种情况讨论,1)两种动物都还未出现2)两种动物其中出现了一种,这都可以分为第一个动物出现了,第二个动物没有出现和第二个动物出现了而第一个动物还没有出现3)两种动物都出现了,但是还未发生关系4)两种动物都出现了,且已经有了关系。其中第1)2)3)三种情况都必须利用上述表达式建立动物之间的联系,并且必有真,而第四种情况则可以直接利用关系判断真假。具体的看代码,下面是代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Max 50010
int set[Max]; //标记
bool exist[Max]; //是否已经出现
int horizon[Max]; //处于哪个关系中,初始值为1
int n,k; 
int main(){
	scanf("%d%d",&n,&k);
	memset(exist,0,sizeof(exist)); //初始化为0,表示都未出现
	memset(set,0,sizeof(set)); //初始化为0?
	int pivot,from,to;
	int sum=0; //记录假话个数
	int index=1; //初始化为第一个关系
	while(k--){
		scanf("%d%d%d",&pivot,&from,&to);
		if((from>n || to>n) ||(pivot==2 && from==to)){ //前两个判断条件
			sum++;
			continue;
		}
		if(!exist[from] && !exist[to]){ //若两个动物都未出现
			exist[from]=exist[to]=1; //标记出现
			horizon[from]=horizon[to]=index; //设置关系标号,处于哪个关系中
			if(pivot==1)
				//根据表达式,设置关系,若是同一类则均设置为0,若A吃	  B则置A为0,B为1?
				set[from]=set[to]=0;
			else{
				set[from]=0;
				set[to]=1;
			}
			index++; //关系标号自增
		}
		else if(exist[from] && !exist[to]){ //若第一种动物出现过,则利用关系将第二个动物加入到第一个动物的所处的关系中去?
			exist[to]=1;
			horizon[to]=horizon[from];
			if(pivot==1)
				set[to]=set[from];
			else
				set[to]=(set[from]+1)%3;
		}
		else if(!exist[from] && exist[to]){//同上
			exist[from]=1;
			horizon[from]=horizon[to];
			if(pivot==1)
				set[from]=set[to];
			else
				set[from]=(set[to]+2)%3;
		}
		else if(exist[from] && exist[to] && horizon[from]==horizon[to]){//若都出现了,并且在同一关系中,则直接利用关系判断真假
			if(pivot==1 && set[from]!=set[to])
				sum++;
			if(pivot==2 && set[from]!=(set[to]+2)%3)
				sum++;
		}
		else if(exist[from] && exist[to] && horizon[from]!=horizon[to]){ //若都出现,并并非出现在同一关系中,则要将两个动物所处的不同关系合并为一个关系,这样就需要利用到表达式了
			int temp=set[to];
			set[to]=(set[from]+pivot-1)%3; //这里统一将第二个动物的关系圈合并到第一个动物的关系圈?
				for(int i=1;i<=n;i++) //逐一的将第二个关系圈的动物与第一个关系圈的动物的关系创建
					if(exist[i] && i!=to && horizon[i]==horizon[to]){ //创建的关键就是,第二个关系圈中的动物与to编号动物原本是什么关系合并后还是什么关系
						if(set[i]==(temp+1)%3)
							set[i]=(set[to]+1)%3;
						else if(set[i]==(temp+2)%3)
							set[i]=(set[to]+2)%3;
						else
						    set[i]=set[to];
						horizon[i]=horizon[from];
					}
			horizon[to]=horizon[from];
		}
	}
	printf("%d\n",sum);
	return 0;
}
		
	

经过测试上述算法是正确的,但却不是最理想的,因为该算法的时间消耗比较大,主要在最后一种最复杂的情况:即两个动物都出现过,但是不再同一个关系中,在合并操作时,每次要遍历n遍,判断与to标号动物处于同一个关系中的动物。

          既然是并查集的典型题目,那么更优化的算法应该是并查集,用并查集和上述方法的本质是一样的,都是将可以确定关系的动物合并在一起,由此来判断后续话的真假,而关系表达式也完全一样,通过上述的三个表达式来与三种关系一一对应。那么并查集的思路就是若两个动物在同一个关系中则直接判断话的真假,否则利用上述表达式将两种关系合并起来,使其成为一种关系。而动物之间的关系是通过它们的深度来体现的,即将上面算法的set理解为这里的deep,注意合并的关键我会在代码中写出来,下面是代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Max 50010
int set[Max];
int deep[Max];
int n,k;
int find(int x){ //路径压缩(节点深度更新)
	if(x==set[x])
		return x;
	int temp=x;
	x=find(set[x]);
	deep[temp]=(deep[temp]+deep[set[temp]])%3;//注意这里一定要先写deep[temp]...再写set[temp],
//否则set[temp]的值会先变化,无法达到节点深度更新的效果
	set[temp]=x;
	return x;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
		set[i]=i;
	memset(deep,0,sizeof(deep));
	int pivot,a,b;
	int count=0;
	while(k--){
	   scanf("%d%d%d",&pivot,&a,&b);
	   if(a>n || b>n || (pivot==2 && a==b)){
		   //printf("error1\n");
		   count++;
		   continue;
	   }
	   int x=find(a);
	   int y=find(b);
	   if(x!=y){
		   if(pivot==1){
			   set[x]=y;
			   deep[x]=(deep[b]-deep[a]+3)%3; //这个是合并的关键,通过将两个动物的根节
//点联系起来,来合并两种关系。这里有一个转化是:(deep[x]+deep[a])%3=deep[b],因为两个动物是同一类,
//那么合并为一个关系后,两者的深度要相同,故有上述表达式,当然是要在路径压缩(节点深度更新)后,进行
//的转化,而且必须要在路径压缩(节点深度更新)后进行这样的转化(思考为什么?),
//将表达式转化为:deep[x]=(deep[b]-deep[a]+3)%3;
		   }
//同样这里也存在一个转化,(deep[x]+deep[a])%3=(deep[b]+1)%3,转化为deep[x]=(deep[b]+1-deep[a]+3)%3;
		   else{
			   set[x]=y;
			   deep[x]=(deep[b]-deep[a]+4)%3;
		   }
	   }
	   else
		   if(deep[a]!=(deep[b]+pivot-1)%3) //将判断的形式统一起来
			   count++;
	}
	printf("%d\n",count);
	return 0;
}
		   
			   
		   
	   
		   

这题并查集的难点在于路径压缩和节点深度更新操作以及如何将两种不同关系合并成一个关系,其中用到了两个转化,但归根结底还是代数表达式和相应关系的正确理解。无论是关系的合并还是节点更新都是根据代数表达式决定的。另外我开始时写了一个错误代码,就是没有正确掌握关系合并的转化式。也贴出来看看:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Max 500010
int parent[Max];
int deep[Max];
bool flag[Max];
int n,k;
int find(int x){
	if(x==parent[x])
		return x;
	int temp=x;
	x=find(parent[x]);
	deep[temp]=(deep[parent[temp]]+deep[temp])%3;
	parent[temp]=x;
	return x;
}
int main(){
	scanf("%d%d",&n,&k);
	memset(flag,0,sizeof(flag));
	memset(deep,0,sizeof(deep));
    for(int i=1;i<=n;i++)
		parent[i]=i;
	int pivot,a,b;
	int sum=0;
	while(k--){
		scanf("%d%d%d",&pivot,&a,&b);
		if(a>n || b>n || (pivot==2 && a==b)){
			printf("error 1\n");
			sum++;
			continue;
		}
		if(!flag[a] && !flag[b]){
			if(pivot==1){
				parent[a]=b;
			    deep[a]=deep[b]=0;
				flag[a]=flag[b]=1;
			}
			else{
				parent[a]=b;
				deep[a]=1;
				deep[b]=0;
				flag[a]=flag[b]=1;
			}
		}
		else if(flag[a] && flag[b]){
			int x=find(a);
			int y=find(b);
			if(x==y && deep[a]!=(deep[b]+pivot-1)%3){
				//printf("error2\n");
				sum++;
			}
			else if(x!=y){
				if(pivot==1){
					parent[x]=b;
					deep[x]=0;
				}
				else{
					parent[x]=b;
					deep[x]=1;
				}
			}
		}
		else{
			if(pivot==2){
			int temp=find(a);
			parent[temp]=b;
			deep[temp]=1;
			flag[a]=flag[b]=1;
			}
			else{
				int temp=find(a);
				parent[temp]=b;
				deep[temp]=0;
				flag[a]=flag[b]=1;
			}
		}
	}
	printf("%d\n",sum);
    return 0;
}
		





 


         
   

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值