poj 1182 食物链 带权并查集

神奇&坑的一题。中文题题意不解释,注意这里输入只有一组数据,EOF会报WA。详解请参考http://blog.csdn.net/c0de4fun/article/details/7318642

这里看两点:1,处理路径压缩时候的关系。2,集合合并时候的关系,当然1 是 2 的前提

1.首先定义节点之间的关系(这里的关系就是a == b, a 吃 b, b 吃 a ,a 是 b 父亲),那么定义x = a - b; 有前面得x = 0表示a,b同属一物种,x = 1表示a 吃 b ,x = 2表示 b 吃 a

那么显然可以得到下面两点:

  1. ani [ now ].parent = ani [ ani [ now ].parent ].parent;     //即当前儿子的祖先节点是其父亲的祖先节点
  2. ani [ now ] .relation = ( ani [ now ] .relation + ani [ now.parent ] .relation ) % 3;   //通过枚举儿子,父亲,及父亲的祖先节点的关系(这里的关系就是a == b, a 吃 b, b 吃 a)

2.在集合合并的时候,如下图,当前的两个节点是x, y,那么合并x,y所在集合,在普通并查集里面就是将y的祖先节点,也就是b,指向x的祖先节点,也就是a(a, b都由1的路径压缩得到),但是我们怎么确定a 和 b 的吃与被吃间的关系呢?

由已经得到答案的问题1可以知道,当知道儿子与父亲关系,父亲与父亲的祖先关系,可以推得儿子与父亲的祖先的关系。而在这里,我们已经知道b是y 路径压缩过的祖先,直接当成父亲看也可以,根据b 与 y之间的关系(假设b 吃 y),那么逆推下,即看成 y 是 b的儿子,则b 与 y的关系 x 就变成 x = ( 3 - x ) %  3,那么就如下图一样,我们可以先有b(儿子)->y(父亲)>x(爷爷)的关系将b连在x上,再由b > x -> a的关系确定b 与 a的关系。 以上,将y 的祖先 b 的父亲指向x 的祖先 a,并确定b 与 a的关系,就成功的合并x, y两个点所在的集合了

再谈想法正确性:假设x, y所属的不相关两个集合里面的关系都是自洽的,那么合并两个集合之后得到的新集合是否还是自洽的?

我们假设 x 集合里面有个点z,与x所在集合是满足关系的,而与y所属集合里面有些点矛盾,例如w,那么由z, w之前就应该合并为同一个集合,而z, w,分别从属两个不相关的集合,显然矛盾,所以假设不成立

最后如何计算假话的数量? 我们只能从已知条件推得当前说的话是否成立,当前条件就是询问的两个点从属同一个集合,不从属的话直接合并两个点就行了。对于两种关系d = 1, d = 2,通过枚举得到,如果是同一个物种,则有x1 = x2,否则就是x1 = x2 + 1

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
#define pi acos(-1.0)
#define eps 1e-8
#define asd puts("sdasdasdasdasd");
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 50050;

struct node{
	int fa, re, x;
	//re for relation, 0 for same, 1 for father eat son, 2 for son eat father
}a[N];
int n, d, k;

void init()
{
	for( int i = 1; i <= n; ++i )
	{
		a[i].x = i;
		a[i].fa = i;
		a[i].re = 0;
	}
}

int dfs( node &t )
{
	if( t.x == t.fa )
		return t.x;
	int fa = t.fa;
	//printf("Animal %d s Parent is %d\n",t.x,t.fa); 
	t.fa = dfs( a[fa] );
	t.re = ( t.re + a[fa].re ) % 3;
	return t.fa;
}

void Union( int x, int y, int xx, int yy, int d )
{
	a[yy].fa = xx;
	a[yy].re = ( (3 - a[y].re) + (d - 1) + a[x].re ) % 3;
}

int main()
{
	//while( ~scanf("%d%d", &n, &k) )
	scanf("%d%d", &n, &k);
	{
		init();
		int x, y, ans = 0;
		while( k-- )
		{
			scanf("%d%d%d", &d, &x, &y);
			if( x > n || y > n || ( d == 2 && x == y ) )
			{
				ans++;
				continue;
			}
			int xx = dfs( a[x] );
			int yy = dfs( a[y] );
			if( xx != yy )	// not same set
			{
				Union( x, y, xx, yy, d );
				continue;
			}
			if( d == 1 )
			{
				if( a[x].re != a[y].re )
					ans++;
			}
			else
			{
				if( ( a[y].re + 3 - a[x].re ) % 3 != 1 )
					ans++;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值