POJ1182食物链 & POJ. 1161TheSuspects

 POJ1182食物链:

这道题是并查集中比较难的一道。并查集究其本身而言并不难(但是如果像算法导论那本书上证明一大堆的那就另论了)。按照我的理解,并查集对元素之间的关系就行分类、合并(不能解离)、查找,从而确定各元素之间的关系。因此,并查集主要用到四个函数:

1)        初始化:将每个元素的父根指向自己,即自己作为一个集合;设置自己的秩为0(秩为该节点高度的上界)。

2)        寻找:通过递归或循环的方式找到自己所在集合的代表,即自己的总父根。

3)        联合:这个函数是并查集中比较重点的做法。用数组实现的并查集有两种优化的方法:按秩合并和路径压缩。这里的联合使用的是按秩合并,先通过寻找函数找到两个点的总父根。如果两个根没有相同的秩,就让较大秩的根称为较小秩的根的父节点,但秩本身保持不变。另一种情况是两个根有相同的秩时,任意选择两个根中的一个作为父节点,并使其秩加1.

4)        判断是否在同一个集:并查集中其实相当于设置了一个元素(总父根)来代表这个集。因为我们只要找到我们要对比的元素分别的代表元素,判断这两个代表元素是否相同就行。

 

本道食物链题目中,我是参照了《挑战程序设计竞赛》中的代码写的。因此我就来讲解一下这份代码。这个实现中比较难也算是比较巧妙的一点,就是处理各个动物在三种分类中的方法:对于每只动物i创建3个元素i-A,i-B,i-C,并用这3*N个元素建立并查集。这里的i-x表示“i属于种类x”,并查集中每一个组表示组内所有元素代表的情况都同时发生或同时不发生。

举个例子,假如有一条信息是说x吃y,那x吃y的情况有什么呢?x是A,y是B;x是B,y是C;x是C,y是A;然后就转换成编程语言,即:x-A,y-B应该在同一集;x-B,y-C在同一集;x-C,y-A在同一集,然后就把以上这三种情况和在一起。

在每次合并之前都先判断这样合并有没有问题,也就是说会不会对之前已经假定是对的情况发生冲突(这里我想到一点是这道题以及不少并查集的题目没考虑的,即会不会产生一个最少错误信息的情况?比如说我之前一句话虽然被判断成正确,但却会影响后面的两句话成错误的,如果把前面那句话置错的话,会不会计算得到一种“错误最少”的情况,可惜书上没有给出答案,我也想不出来。希望有想过这方面的读者能和我交流交流)。比如说还是上面的例子,既然x能吃y,那说明不可能出现x是A,y是C,或者x,y是同一类,即:x-A,y-C应该在同一集;x, y ; x-A, y-A ; x-B, y-B ; x-C, y-C应该在同一集。

 

另外注意这道题有两个天坑,坑了我一整天。这道题只能输入一组数据,所以递交的时候或者写的时候就不要用while来输入n和k了;其次数据规模要开大一点,虽然k是100000以内,但建议开到600000,我是100010的时候不过,600010的时候过了。


/*
 * POJ1182.cpp
 *
 *  Created on: 2014年7月9日
 *      Author: Prophet
 */
#include<stdio.h>
const int maxk = 100100;
int N,K;
int T[maxk],X[maxk],Y[maxk];
int par[maxk],rank[maxk];
void init(int n);
int find(int x);
void unite(int x,int y);
bool same(int x,int y);
int main(){
	//while(scanf("%d%d",&N,&K)!=EOF){
		scanf("%d%d",&N,&K);
		int NumOfLies=0;
		init(3*N);
		for(int i=0;i<K;i++){
			scanf("%d",&T[i]);
			scanf("%d",&X[i]);
			scanf("%d",&Y[i]);
		}

		for(int i=0;i<K;i++){
			int t = T[i];
			int x = X[i]-1,y=Y[i]-1;

			//不正确编号
			if(x<0||N<=x||y<0||N<=y){
				NumOfLies++;
				continue;
			}

			if(t==1){
				if(same(x,y+N)||same(x,y+2*N)){//检查x-A和y-B(y-C)是否在一个集中,如果任一满足那x,y肯定不属于同一类
					NumOfLies++;
				}
				else{
					unite(x,y);
					unite(x+N,y+N);
					unite(x+2*N,y+2*N);
				}
			}
			else{
				//if(same(x,y)||same(x+N,y+N)||same(x+2*N,y+2*N)||same(x,y+2*N)){//检查x-A和y-A、x-A和y-C是否在一集中,如果任一满足那x肯定吃不了y
				if(same(x,y)||same(x,y+2*N)){
					NumOfLies++;
				}
				else{
					unite(x,y+N);
					unite(x+N,y+2*N);
					unite(x+2*N,y);
				}
			}
		}
		printf("%d\n",NumOfLies);
	//}
	return 0;
}

void init(int n){
	for(int i=0;i<n;i++){
		par[i]=i;
		rank[i]=0;
	}
}

int find(int x){
	if(par[x]==x)
		return x;
	else
		return par[x]=find(par[x]);
}

void unite(int x,int y){
	x = find(x);
	y = find(y);
	if(x==y)
		return;

	if(rank[x]<rank[y])
		par[x] = y;
	else{
		par[y] = x;
		if(rank[x] == rank[y])
			rank[x]++;
	}
}

bool same(int x,int y){
	return find(x) == find(y);
}


POJ. 1161TheSuspects

这道题属于比较基础的并查集,一次次搜索合并集合就可以了。但是需要另外多开一个num数组来记录每个集合中数目的个数。


/*
 * POJ1161TheSuspects.cpp
 *
 *  Created on: 2014年7月10日
 *      Author: Prophet
 */
#include<stdio.h>
int n,m;
const int maxn = 30005 ;
int SecondStudent;
int par[maxn],rank[maxn];
int num[maxn];//记录每个元素所在的集的元素
void init(int n);
int find(int x);
void unite(int x,int y);
bool same(int x,int y);
int main(){
	while(scanf("%d%d",&n,&m),n+m>0){
		if(m==0){
			printf("1\n");
			continue;
		}
		init(n);
			for(int j=0;j<m;j++){
				int StudentNum,FirstStudent;
				scanf("%d",&StudentNum);
				scanf("%d",&FirstStudent);
				for(int i=1;i<StudentNum;i++){

					scanf("%d",&SecondStudent);
					unite(FirstStudent,SecondStudent);
					FirstStudent=SecondStudent;
				}
			}
		int top = find(0);
		/*for(int i=0;i<n;i++)
			printf("par[%d] is : %d\n",i,par[i]);*/
		printf("%d\n",num[top]);
	}
	return 0;
}

void init(int n){
	for(int i=0;i<n;i++){
		par[i]=i;
		rank[i]=0;
		num[i]=1;
	}
}

int find(int x){
	if(par[x]==x)
		return x;
	else
		return par[x]=find(par[x]);
}

void unite(int x,int y){
	x = find(x);
	y = find(y);
	if(x==y)
		return;

	if(rank[x]<rank[y]){//将y作为父节点,因此把x的父亲“指向”y,并把原来x集的元素个数加到y里面
		par[x] = y;
		num[y]+=num[x];
	}

	else{
		par[y] = x;
		if(rank[x] == rank[y])
			rank[x]++;
		num[x]+=num[y];
	}
}

bool same(int x,int y){
	return find(x) == find(y);
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值