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");
}
}