【poj1417 True Liars 】【带权并查集】【01背包】【打印路径】【繁】

https://vjudge.net/problem/12603/origin

【题意】

题目大意:给你p1个好人和p2个坏人,编号为1-p1+p2,然后给你n中操作

                  x1 x2 no:x1说x2不是好人

                  x1 x2 yes:x1说x2是好人

                  在这里好人说的总是对的,坏人说的总是坏的,然后问你最后能不能唯一确定哪些是好人,并输出不能就输出no

【思路】

显然的一个带权并查集的应用,通过并查集,我们可以将所有人分成几个大集合

每个大集合内部有关系制约,不同大集合之间没有关系

对于每个人的“权”,我们会在大集合中分成两个小集合,相同小集合表示同类

那么问题转化为:在每个大集合中选择两个小集合中的一个,看是否能组成好人p1个

典型的01背包,dp[i][j]表示前i个大集合有j个好人是否能够组成

然后就可以解决了

再路径打印一下即可

 

【代码】

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;

int f[maxn], rk[maxn], p[maxn], vis[maxn];
int w0[maxn], w1[maxn], dp[maxn][maxn];//dp[i][j]表示前i个大集合,能否构成j个好人
int n, p1, p2;

void init(){
	for (int i = 1; i <= p1 + p2; i++){
		f[i] = i;
		rk[i] = 0;
		vis[i] = 0;
		w0[i] = w1[i] = 0;
	}
	memset(dp, 0, sizeof dp);
}

int find(int x){
	if (x != f[x]){
		int temp = f[x];
		f[x] = find(f[x]);
		rk[x] = rk[x] ^ rk[temp];
	}
	return f[x];
}

void mix(int x, int y, int k){
	int xx = find(x);
	int yy = find(y);
	if (xx != yy){
		f[xx] = yy;
		rk[xx] = rk[x] ^ rk[y] ^ k;
	}
}

int main(){
	while (scanf("%d %d %d", &n, &p1, &p2) && (n + p1 + p2)){
		init();
		for (int i = 0; i<n; i++){
			int a, b;
			char str[10];
			scanf("%d %d %s", &a, &b, str);
			if (str[0] == 'y')
				mix(a, b, 0);
			else
				mix(a, b, 1);
		}
		int cnt = 1;
		for (int i = 1; i <= p1 + p2; i++){
			if (!vis[i]){
				int fa = find(i);
				for (int j = i; j <= p1 + p2; j++){
					if (find(j) == fa && !vis[j]){
						vis[j] = 1;
						if (rk[j] == 0)
							w0[cnt]++;
						else
							w1[cnt]++;
					}
				}
				p[cnt] = fa;
				cnt++;
			}
		}
		dp[0][0] = 1;
		for (int i = 1; i<cnt; i++){
			int minn = min(w0[i], w1[i]);
			for (int j = p1; j >= minn; j--)
				dp[i][j] |= dp[i - 1][j - w0[i]]| dp[i - 1][j - w1[i]];
		}

		if (dp[cnt - 1][p1] != 1){
			puts("no");
			continue;
		}
		vector<int>ans;
		int num = 0;
		int good = p1;
		for (int i = cnt - 1; i; i--){
			if (dp[i - 1][good - w0[i]] == 1){
				for (int j = 1; j <= p1 + p2; j++)
					if (find(j) == p[i] && rk[j] == 0)
						ans.emplace_back(j);
				good -= w0[i];
			}
			else
			{
				for (int j = 1; j <= p1 + p2; j++)
					if (find(j) == p[i] && rk[j] == 1)
						ans.emplace_back(j);
				good -= w1[i];
			}
		}
		sort(ans.begin(), ans.end());
		for (int x : ans) {
			printf("%d\n", x);
		}
		puts("end");
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值