POJ-1182 食物链【带权并查集】

题目链接:http://poj.org/problem?id=1182

解题思路:

这道题是并查集题目中的经典。。。而且比普通并查集提高了一个档次,下面在基础并查集的前提上讲解并查集的真正用法。

基础回顾:

find()函数找根结点的两种写法如下:

第一种递归:

int find(int x)
{
	return x == pre[x] ? x : find(pre[x]);
}

第二种:

int find(int x)
{
	int root, temp;
	root = x;
	while(root != pre[root])
		root = pre[root];
	while(x != root)
	{
		temp = pre[x];
		pre[temp] = root;
		x = temp;
	}
	return root;
}

上面2种是最基本的查找操作。

下面我们通过这道题来讲解一下并查集的深层次应用。

输入:动物个数n以及k句话,接着输入k行,每一行形式为:d x y,在输入时可以先判断题目所说的条件2和3,即:
       1>若(x>n||y>n):即当前的话中x或y比n大,则假话数目sum加1.
       2>若(x==2&&x==y):即当前的话表示x吃x,则假话数目sum加1.

而不属于这两种情况外的话语要利用并查集进行判断当前的话是否与此前已经说过的话相冲突.

struct node
{
	int parent;                     //p[i].parent表示节点i的父节点
	int relation;                   //p[i].relation表示节点i与其父节点(即p[i].parent)的关系
}p[50010];

  初始化函数为: 

void init(int n)
{
	int i;
	for(i = 1;i <= n; ++i)
	{
		p[i].parent = i;            //初始时集合编号就设置为自身
		p[i].relation = 0;        //因为p[i].parent=i,即节点i的父亲节点就是自身,所以此时节点i与其父亲节点的关系为同类(即p[i].relation=0)
	}
}

查找操作:

合并操作:

 

关系域更新:

当然,这道题理解到这里思路已经基本明确了,剩下的就是如何实现,在实现过程中,我们发现,更新关系域是一个很头疼的操作,网上各种分析都有,但是都是直接给出个公式,至于怎么推出来的都是一笔带过,让我着实头疼了很久,经过不断的看discuss,终于明白了更新操作是通过什么来实现的。下面讲解一下

仔细再想想,rootx-x 、x-y、y-rooty,是不是很像向量形式?于是我们可以大胆的从向量入手:

tx       ty

|          |

x   ~    y

对于集合里的任意两个元素x,y而言,它们之间必定存在着某种联系,因为并查集中的元素均是有联系的(这点是并查集的实质,要深刻理解),否则也不会被合并到当前集合中。那么我们就把这2个元素之间的关系量转化为一个偏移量(大牛不愧为大牛!~YM)。

由上面可知:
x->y 偏移量0时 x和y同类

x->y 偏移量1时 x被y吃

x->y 偏移量2时 x吃y

有了这个假设,我们就可以在并查集中完成任意两个元素之间的关系转换了。

不妨继续假设,x的当前集合根节点rootx,y的当前集合根节点rooty,x->y的偏移值为d-1(题中给出的询问已知条件)

(1)如果rootx和rooty不相同,那么我们把rooty合并到rootx上,并且更新relation关系域的值(注意:p[i].relation表示i的根结点到i的偏移量!!!!(向量方向性一定不能搞错)

    此时 rootx->rooty = rootx->x + x->y + y->rooty,这一步就是大牛独创的向量思维模式

    上式进一步转化为:rootx->rooty = (relation[x]+d-1+3-relation[y])%3 = relation[rooty],(模3是保证偏移量取值始终在[0,2]间)

(2)如果rootx和rooty相同(即x和y在已经在一个集合中,不需要合并操作了,根结点相同),那么我们就验证x->y之间的偏移量是否与题中给出的d-1一致

    此时 x->y = x->rootx + rootx->y

    上式进一步转化为:x->y = (3-relation[x]+relation[y])%3,
    若一致则为真,否则为假

最后总结一下:如果x,y不在一个集合里面,说明他们之间还没有构成任何关系,就需要把他们两个合并,

如果x,y已经在一个集合里面,说明他们之间已经构成了某种关系(同类,吃,被吃),然后用已经构建好的relation和输入的d进行比较就行了,相等 就是真话,不相等就是假话。

分析到这里,这道题已经从思想过渡到实现了。剩下的就是一些细节问题,自己处理一下就好了。

PS:做完这题,就可以去秒了大部分基础的并查集了,嘿嘿大笑

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define Max 50010

struct node{
	int pre;
	int relation;
};
node p[Max];
int n,k;
	int d,a,b;
	int rootx,rooty;
	int sum=0;
int find(int x)
{
	int tmp = p[x].pre;
	if(x==tmp) return x;
	p[x].pre=find(tmp);
	p[x].relation=(p[x].relation+p[tmp].relation)%3;
	return p[x].pre;
}
void init()
{
	for(int i=0;i<Max;i++)
	{
		p[i].relation=0;
		p[i].pre = i;
	}
	sum=0;
}
void Union(int x,int y)
{
	p[rootx].pre = rooty;
	p[rootx].relation = (-p[x].relation+d-1+p[y].relation+3)%3;
}
int main()
{
	
	scanf("%d%d",&n,&k);
	init();
	while(k--)
	{
		scanf("%d%d%d",&d,&a,&b);
		if(a>n || b>n)
		{
			sum++;
			continue;
		}
		if(d==2 && (a==b))
		{
			sum++;
			continue;
		}
		rootx = find(a);
		rooty = find(b);
		if(rootx != rooty)
		{
			Union(a,b);
		}
		else
		{
			if(d==1) 
			{
				if(p[a].relation != p[b].relation)
				{
					sum++; 
					continue;
				}
				
			}
			if(d==2 && ((p[a].relation-p[b].relation+3)%3 !=d-1))
			{
				sum++;
				continue;
			}
		}
		
	}
	cout<<sum<<endl;
	return 0;
}

转载链接:https://blog.csdn.net/niushuai666/article/details/6981689

猜你喜欢

转载自blog.csdn.net/qq_40816078/article/details/82769324