POJ 1417 True Liars 带权并查集+DP

来源:互联网 发布:淘宝hd ipad历史版本 编辑:程序博客网 时间:2024/05/21 06:46

题目:

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
原创粉丝点击