HDU 1829 A Bug's Life

传送门

带权并查集。题目先假设同性之间不发生性行为,然后给出很多对昆虫的性行为记录,问你从这些记录中能不能推得假设错误。

有两种方法,第一种方法思路简单,第二种方法类似POJ 1182

这种题就是根据记录1 ~ i-1作为已知信息,判断当前记录i是否与之冲突。
其实判断的只是造成假设错误的充分条件,但这就已经符合题目要求了,也无法做更多判断。

(在第二种方法中思考这句话比较直观)其实,只有当该条记录的两点可以被之前记录的点连起来了(不仅仅是都出现过),才能真正地对这条记录作出判断。

1.

这种方法直接判断每条记录的两点是否同性。用并查集保存一个个集合,每个集合内性别相同。(不同集合之间性别关系未知,无法判断,即默认不冲突)

利用且只有利用情敌关系构建同性集合,情敌包括直接情敌和间接情敌,举一个间接情敌的例子:ab是直接情敌、都连接dbc是直接情敌、都连接e,那么ac就是间接情敌。

sex[]记录每个点的原配,所以把点x之后出现的对象都和原配sex[x] u一下就完事了。可以保证情敌关系(直接或间接)一定在一个集合内。

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;

const int MAXN = 2001;

int T, N, M;
int pre[MAXN];
int sex[MAXN];
bool flag;

void init()
{
	memset(pre, -1, sizeof pre);
	memset(sex, 0, sizeof sex);
	flag = false;
}

int f(int x)
{
	if (pre[x] < 0) return x;
	return pre[x] = f(pre[x]);
}

void u(int a, int b)
{
	int f1 = f(a);
	int f2 = f(b);
	if (f1 == f2) return;
	if (pre[f1] <= pre[f2])
	{
		pre[f1] += pre[f2];
		pre[f2] = f1;
	}
	else
	{
		pre[f2] += pre[f1];
		pre[f1] = f2;
	}
}

int main()
{
	int a, b;
	scanf("%d", &T);
	for (int i = 1; i <= T; i++)
	{
		init();
		scanf("%d%d", &N, &M);
		for (; M--;)
		{
			scanf("%d%d", &a, &b);
			if (flag) continue;
			if (f(a) == f(b))
			{
				flag = true;
				continue;
			}
			if (!sex[a]) sex[a] = b;
			else u(sex[a], b);
			if (!sex[b]) sex[b] = a;
			else u(sex[b], a);
		}

		printf("Scenario #%d:\n", i);
		if (flag) printf("Suspicious bugs found!\n\n");
		else printf("No suspicious bugs found!\n\n");
	}

	return 0;
}

2. 参考POJ 1182

再写一种,这种方法的思想比较难写出来。我的理解,sign数组的意义是不确定的,有时指与父节点的一致情况,有时指与根节点的一致情况,当然,相对来说,其实都是指与父节点的一致情况。要搞清这个,关键在于理解find函数的递归过程,首先符合压缩规则这没问题,

sign[x] = (sign[x] + sign[pre[x]]) & 1;

这一句完成了sign意义的转化,sign[x]旧值指与父节点的一致情况(上一次被赋值时是直接指向根节点,然而当时的根节点已不是现在的根节点),sign[x]新值是指与根节点的一致情况,pre[x]在现在还是指父节点(马上下一句就变成根节点了),因为父节点在上次递归返回中已被直接指向根节点,所以sign[pre[x]]的意义及值也得到更新,所以sign[pre[x]]是指父节点与根节点的一致情况,所以,根据父节点过渡,负负得正,得到当前节点与根节点的一致情况。
所以,find函数先根据访问点层层上升直到根节点,然后从根节点下降,把沿途经过的每个结点直接指向根节点并赋予其正确的与根节点的一致情况。

接下来我说一说union/merge函数,之前我想的是,首先合并肯定不会涉及flag,合并是根1直接指向根2,那么根1及其子树可能都要变sign,然而,只用更新根1的sign就行了,因为每次查询根的时候都会把链粉碎并自上而下地赋予sign,所以只要保证子树的根 根1sign正确就行了。那么,原先的两个查询点n1 n2已经直接指向根1根2,且n1 n2一定不一致,根1到根2的一致情况也就可以通过下式确定了。

sign[f1] = (sign[n1] + sign[n2] + 1) & 1;

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;

const int MAXN = 2001;
int T, N, M;
int pre[MAXN];
int sign[MAXN];                                   // 0表示一致,1表示不一致
bool flag;

void init()
{
	memset(pre, -1, sizeof pre);
	memset(sign, 0, sizeof sign);
	flag = false;
}

int f(int x)
{
	if (pre[x] < 0) return x;
	int t = f(pre[x]);
	sign[x] = (sign[x] + sign[pre[x]]) & 1;       // 也可以写成 sign[x] ^ sign[pre[x]]
	return pre[x] = t;
}

void u(int n1, int n2)
{
	int f1 = f(n1);
	int f2 = f(n2);
	if (f1 == f2)
	{
		if (sign[n1] == sign[n2])
			flag = true;
		return;
	}
	if (pre[f1] <= pre[f2])
	{
		pre[f1] += pre[f2];
		pre[f2] = f1;
		sign[f2] = (sign[n1] + sign[n2] + 1) & 1; // 也可以写成 !(sign[n1] ^ sign[n2])
	}
	else
	{
		pre[f2] += pre[f1];
		pre[f1] = f2;
		sign[f1] = (sign[n1] + sign[n2] + 1) & 1;
	}
}

int main()
{
	int a, b;
	scanf("%d", &T);
	for (int i = 1; i <= T; i++)
	{
		init();
		scanf("%d%d", &N, &M);
		for (; M--;)
		{
			scanf("%d%d", &a, &b);
			if (flag) continue;
			u(a, b);
		}

		printf("Scenario #%d:\n", i);
		if (flag) printf("Suspicious bugs found!\n\n");
		else printf("No suspicious bugs found!\n\n");
	}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值