POJ 1417 True Liars 带权并查集+DP

题目:

http://poj.org/problem?id=1417

题意:

有两种人:好人和坏人,其中好人说的话一定是真的,坏人说的话一定是假的。现在知道好人和坏人的具体个数,又提问了n个问题:x y yes|no,问第x个人,第y个人是好人还是坏人,回答yes或者no。问根据以上能不能判断出哪些人是好人,题目保证不会有矛盾的问答

思路:

可以根据问答把所有人分类,每类又分成两小类:相互矛盾的两小类,相互矛盾的两小类必定有一类是好人一类坏人,但具体哪一类是好人哪一类是坏人并不知道。然后可以就是看能不能从每一类中当且仅当拿出一个小类凑一起,使人数恰好等于好人的人数,且这个方案数只有一种。可以发现,分类用带权并查集实现,后面凑人数用背包记录方案数和路径可以实现。本人的代码写的太挫了,惨不忍睹,不想改了 。。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <set>
using namespace std;

const int N = 700 + 10, INF = 0x3f3f3f3f;

int par[N], rnk[N];
int w[N][2];
int dp[N][N], pre[N][N], p[N], rp[N];
void init(int n)
{
    for(int i = 1; i <= n; i++) par[i] = i, rnk[i] = 0;
}
int ser(int x)
{
    if(x != par[x])
    {
        int fx = ser(par[x]);
        rnk[x] = (rnk[x] + rnk[par[x]]) % 2;
        par[x] = fx;
    }
    return par[x];
}
void unite(int x, int y, int type)
{
    int fx = ser(x), fy = ser(y);
    if(fx == fy) return;
    rnk[fy] = (rnk[y] + type + rnk[x]) % 2;
    par[fy] = fx;
}
int main()
{
    int n, p1, p2;
    while(scanf("%d%d%d", &n, &p1, &p2), n || p1 || p2)
    {
        init(p1 + p2);
        int x, y;
        char s[10];
        for(int i = 1; i <= n; i++)
        {
            scanf("%d%d%s", &x, &y, s);
            if(s[0] == 'y') unite(x, y, 0);
            else unite(x, y, 1);
        }
        memset(w, 0, sizeof w);
        memset(p, -1, sizeof p);
        int cnt = 0, id;
        for(int i = 1; i <= p1+p2; i++)//分类,统计每小类的人数作为物品
        {
            ser(i);
            if(p[par[i]] != -1) id = p[par[i]];
            else id = p[par[i]] = ++cnt;
            w[id][rnk[i]]++;
        }
        memset(dp, 0, sizeof dp);
        memset(pre, -1, sizeof pre);
        dp[0][0] = 1;
        for(int i = 1; i <= cnt; i++) //背包求方案数
            for(int j = p1; j >= 0; j--)
            {
                if(j - w[i][0] >= 0)
                {
                    dp[i][j] += dp[i-1][j-w[i][0]];
                    if(dp[i-1][j-w[i][0]] != 0) pre[i][j] = 0;
                }

                if(j - w[i][1] >= 0)
                {
                    dp[i][j] += dp[i-1][j-w[i][1]];
                    if(dp[i-1][j-w[i][1]] != 0) pre[i][j] = 1;
                }

            }
        if(dp[cnt][p1] != 1) //恰好为1说明可以判断,否则不能
        {
            printf("no\n"); continue;
        }
        memset(rp, -1, sizeof rp);
        for(int i = 1; i <= p1+p2; i++) //rp[i]表示第i件物品所属的类别,是p[i]的反向映射
            if(p[i] != -1) rp[p[i]] = i;

        int res[N][2], k = 0, tm = p1;
        memset(res, -1, sizeof res);
        for(int i = cnt; i >= 1; i--)//背包求路径
            if(pre[i][tm] != -1)
            {
                int t = pre[i][tm];
                res[k++][t] = rp[i];
                tm -= w[i][t];
            }
        int ans[N], tot = 0;
        for(int i = 1; i <= p1+p2; i++)
        {
            for(int j = 0; j < k; j++)
            {
                if(res[j][0] != -1 && par[i] == res[j][0] && rnk[i] == 0)
                {
                    ans[tot++] = i; break;
                }
                if(res[j][1] != -1 && par[i] == res[j][1] && rnk[i] == 1)
                {
                    ans[tot++] = i; break;
                }
            }
        }
        sort(ans, ans + tot);
        for(int i = 0; i < tot; i++) printf("%d\n", ans[i]);
        puts("end");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值