True Liars POJ - 1417

题意:

有 p 个好人和 q 个坏人,好人永远说真话,坏人永远说假话。现给出一组话,问能否唯一确定每个人是好人还是坏人。

题解:
  1. 当 a 说 b 是好人时,如果 a 是好人,那么 b 也应该是好人,如果 a 是坏人,那么 b 也应该是坏人
  2. 当 a 说 b 是坏人时,如果 a 是好人,那么 b 就应该是坏人,如果 a 是坏人,那么 b 就应该是好人

那么输入的 opt = "yes"的时候,a 和 b 就应该属于同一个阵营的,否则 a 和 b 应该不属于同一阵营,用并查集维护每个集合中的人即可,但是我们还不知道每个集合中的人到底是好人还是坏人。

我们可以类比背包问题,要么选集合中的好人,要么选集合中的坏人,最后判断得到的好人 = p的方案数是否等于 1 即可,因为答案是唯一的,所以直接跑一次背包,记录之前有多少个好人,现在的好人数量,反向输出方案即可。

代码:
/*
 * @Author : Nightmare
 */
#include <iostream>
#include <time.h>
#include <vector>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long
#define ull unsigned long long
#define ls 2 * rt
#define rs 2 * rt + 1
#define gcd(a,b) __gcd(a,b)
#define eps 1e-8
#define lowbit(x) (x&(-x))
#define N 605
#define M 305
#define mod 1000000007
#define inf 0x3f3f3f3f
int n, p, q;
int fa[N], val[N], num[N][2], vis[N], dp[N][M], pre[N][M];
// num : 第 i 个集合中好人和坏人的个数
// dp  : 前 i 个集合中好人为 j 个的方案数
// pre : 前 i 个集合中好人为 j 个的上一个状态有多少个好人
vector<int> b[N][2];
int find(int x){
    if(fa[x] == x) return x;
    int f = find(fa[x]);
    val[x] = (val[x] + val[fa[x]]) % 2;
    return fa[x] = f;
}
void init(){
    for(int i = 0 ; i < N ; i ++){
        fa[i] = i; val[i] = vis[i] = 0;
        b[i][0].clear(); b[i][1].clear();
        num[i][0] = num[i][1] = 0;
    }
}
void solve(){
    init();
    for(int i = 1 ; i <= n ; i ++){
        int a, b; string opt; cin >> a >> b >> opt;
        int flag = (opt == "no");
        int x = find(a), y = find(b);
        if(x != y){
            fa[x] = y;
            val[x] = (val[b] - val[a] + flag + 2) % 2;
        }
    }
    int cnt = 1;
    for(int i = 1 ; i <= p + q ; i ++){
        if(!vis[i]){
            for(int j = i ; j <= p + q ; j ++){
                if(find(i) == find(j)){
                    vis[j] = 1;
                    b[cnt][val[j]].push_back(j);
                    num[cnt][val[j]] ++;
                }
            }
            cnt ++;
        }
    }
    memset(dp, 0, sizeof(dp)); dp[0][0] = 1;
    for(int i = 1 ; i < cnt ; i ++){
        for(int j = p ; j >= 0 ; j --){
            if(j >= num[i][0] && dp[i - 1][j - num[i][0]]){
                dp[i][j] += dp[i - 1][j - num[i][0]];
                pre[i][j] = j - num[i][0];
            }
            if(j >= num[i][1] && dp[i - 1][j - num[i][1]]){
                dp[i][j] += dp[i - 1][j - num[i][1]];
                pre[i][j] = j - num[i][1];
            }
        }
    }
    if(dp[cnt - 1][p] != 1){ puts("no"); return ; } // 方案数不唯一 或者 无解
    vector<int> ans; int cur = p;
    for(int i = cnt - 1 ; i >= 1 ; i --){
        if(cur - pre[i][cur] == num[i][0]){
            for(int j = 0 ; j < num[i][0] ; j ++)
                ans.push_back(b[i][0][j]);
        }else{
            for(int j = 0 ; j < num[i][1] ; j ++)
                ans.push_back(b[i][1][j]);
        }
        cur = pre[i][cur];
    }
    sort(ans.begin(), ans.end());
    for(int i = 0 ; i < ans.size() ; i ++) printf("%d\n", ans[i]);
    printf("end\n");
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("F:\\in.txt", "r", stdin);
#endif
    while(~scanf("%d %d %d", &n, &p, &q) && (n + p + q)) solve();
#ifndef ONLINE_JUDGE
    cerr << "Time elapsed: " << 1.0 * clock() / CLOCKS_PER_SEC << " s.\n";
#endif
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值