True Liars POJ - 1417(并查集+DP)

POJ—1417
在这里插入图片描述
在这里插入图片描述
题意: 给你p1个好人和p2个坏人,编号为1-p1+p2,然后给你n种操作

x1 x2 no:x1说x2不是好人

x1 x2 yes:x1说x2是好人
在这里好人说的总是对的,坏人说的总是坏的,然后问你最后能不能唯一确定哪些是好人,并输出,否则输出”no“

思路:首先,我们假设x1是好人,并且有 x1 x2 yes 那么,x2一定也是好人,如果有x1 x2 no 那么x2一定是坏人。如果假设x1是坏人,如果有x1 x2 no 那么 x2 一定是好人, 如果有 x1 x2 yes 那么x2也是坏人。也就是说,只要给出的是yes,那么,x1,x2一定是同一类人,否则,一定不是同一类。(自己推一下其中的关系就会明白)。那么,根据这个关系,我们就可以用带权并查集来做了,令同一类为0,否则为1。但是,只用带权并查集的话,只能分出若干个大的集合,每个大的集合又分成两个小集合,即 好人集合 与 坏人集合。但是,我们并不知道每个大集合中,哪个小集合是好人集合哪个集合是坏人集合。这时,就需要我们用dp来处理了。从每个大集合里面取一个当作好人集合,判断方案是否唯一。dp[i][j]表示,前i个大集合有j个好人时的方案的个数,最后,只需判断 对应大集合个数和好人个数时的方案数是否为1即可,不唯一,说明不能确定。(可以发现 a说b是神 则ab同类 反之则异类 这样可以处理出每个连通块内神或魔各有多少个,然后dp[i][j]代表走完前i个连通块后且有j个神的情况有多少 顺带记录一下路径即可 因为要求答案唯一 直接输出路径即可)

#include <stdio.h>
#include<algorithm>
#include<iostream>
#include <math.h>
#include<queue>
#include<string>
#include <string.h>
#define mod 1000000007
#define INF 0xfffffff
#include<time.h>
using namespace std;
#define MAX 1002
#define lowbit(x) (x&(-x))//寻找x管辖的长度
int n, m, a, b;
int fa[MAX];
int val[MAX];
int flag;
int cal[MAX];
int dp[MAX][MAX], pre[MAX][MAX];
int s1[MAX], s2[MAX];
int b1[MAX], b2[MAX];
int col[MAX][MAX];
int Hash[MAX];
int ans[MAX], pos;

int find(int x)
{
	if (x == fa[x])
		return x;
	int fx = fa[x];
	fa[x] = find(fa[x]);
	if (val[x])
		val[x] = !val[fx];
	else
		val[x] = val[fx];
	return fa[x];
}

int Merge(int x, int y, int c)
{
	int fx = find(x);
	int fy = find(y);
	if (fx == fy)
		return 1;
	fa[fy] = fx;
	if ((val[x] ^ val[y]) == c)//不符合
	{
		s1[fx] += s2[fy];
		s2[fx] += s1[fy];
		s1[fy] = s2[fy] = 0;
		val[fy] = !val[fy];
	}
	else
	{
		s1[fx] += s1[fy];
		s2[fx] += s2[fy];
		s1[fy] = s2[fy] = 0;
	}
	return 0;
}

int main()
{
	int k;
	while (cin >> k >> a >> b && (k || a || b))
	{
		n = a + b;
		for (int i = 1; i <= n; i++)
		{
			fa[i] = i;
			s1[i] = 1;
			s2[i] = 0;
			val[i] = 0;
		}
		flag = 0;
		for (int i = 1; i <= k; i++)
		{
			int x, y, c;
			char s[5];
			scanf("%d%d%s", &x, &y, s);
			if (s[0] == 'y') c = 1;
			else c = 0;
			if (flag) continue;
			if (Merge(x, y, c) && (val[x] ^ val[y]) == c)
			{
				flag = 1;
			}
		}
		int m = 0;
		for (int i = 1; i <= n; i++) find(i);
		for (int i = 1; i <= n; i++)
			if (s1[i] || s2[i])
			{
				b1[++m] = s1[i];
				b2[m] = s2[i];
				Hash[i] = m;
				// cout<<b1[m]<<" "<<b2[m]<<endl;
			}
		memset(dp, 0, sizeof(dp));
		dp[0][0] = 1;
		for (int i = 1; i <= m; i++)//集合
			for (int j = n; j >= 0; j--) {//真话人数
				if (j - b1[i] >= 0 && dp[i - 1][j - b1[i]])
				{
					dp[i][j] += dp[i - 1][j - b1[i]];
					pre[i][j] = j - b1[i];
					col[i][j] = 1;
				}
				if (j - b2[i] >= 0 && dp[i - 1][j - b2[i]])
				{
					dp[i][j] += dp[i - 1][j - b2[i]];
					pre[i][j] = j - b2[i];
					col[i][j] = 2;
				}
				if (dp[i][j] > 1) dp[i][j] = 2;
			}
		if (flag || dp[m][a] != 1)
			cout << "no" << endl;
		else
		{
			int j = a;
			for (int i = m; i >= 1; i--)
			{
				cal[i] = col[i][j];
				j = pre[i][j];
			}
			for (int i = 1; i <= n; i++)
				if ((cal[Hash[find(i)]] == 1 && val[i] == 0) || (cal[Hash[find(i)]] == 2 && val[i] == 1))
				{
					cout << i << endl;
				}
			cout << "end" << endl;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值