POJ 1417(带权并查集 + dp 记录路径)

题意:

一个岛上存在着两种居民,一种是天神,一种是恶魔。

天神永远都不会说假话,而恶魔永远都不会说真话。

岛上的每一个成员都有一个整数编号(类似于身份证号,用以区分每个成员)。

现在你拥有n次提问的机会,但是问题的内容只能是向其中一个居民询问另一个居民是否是天神,请你根据收集的回答判断各个居民的身份。

题解:

不难发现,如果输入 a b yes 则表示 a 与 b 在同一个种族, a b no 则表示 a 与 b 在不同的种族。

先用带权并查集处理出各点之间的关系,在一个集合中的点即为确定关系的点,可以分为与根节点距离为 1 的点和距离为 0 的点。距离为 0 的点即为与根节点同种族的点,反之则为不同种族的点。

处理出每个集合之后,问题则转化为若干组物品,每组有两个,问最终能够凑成重量为 p1 的方法数。如果方法数唯一,则表示有解。

可以用 dp 解决。然后记录路径即可。

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
#define int ll
#define PI acos(-1.0)
#define INF 0x3f3f3f3f3f3f3f3f
#define P pair<int, int>
#define fastio ios::sync_with_stdio(false), cin.tie(0)
const int mod = 998244353;
const int M = 1000000 + 10;
const int N = 1000 + 10;

int n, a, b;
int f[N], d[N];
int dp[N][N];

int get(int x)
{
    if(f[x] == x) return x;

    int rt = get(f[x]);
    d[x] = (d[x] + d[f[x]]) % 2;
    return f[x] = rt;
}

signed main()
{
    while(cin >> n >> a >> b, n || a || b) {
        memset(dp, 0, sizeof dp);
        int tot = a + b;
        for(int i = 1; i <= tot; i ++) f[i] = i, d[i] = 0;
        for(int i = 1; i <= n; i ++) {
            int x, y; string s;
            cin >> x >> y >> s;
            int rx = get(x), ry = get(y);
            if(s[0] == 'y') {
                f[rx] = ry;
                d[rx] = (d[y] - d[x] + 2) % 2;
            } else {
                f[rx] = ry;
                d[rx] = (d[y] - d[x] + 1 + 2) % 2;
            }
        }

        int cnt = 0, rtid[N], num[N][2] = {0};
        for(int i = 1; i <= tot; i ++) {
            int ri = get(i);
            if(ri == i)  rtid[ri] = ++cnt;
        }

        for(int i = 1; i <= tot; i ++) {
            int ri = get(i);
            num[rtid[ri]][d[i]] ++;
        }

        dp[0][0] = 1;
        for(int i = 1; i <= cnt; i ++) {
            for(int j = a; j >= num[i][0]; j --) {
                if(dp[i - 1][j - num[i][0]]) {
                    dp[i][j] += dp[i - 1][j - num[i][0]];
                }
            }
            for(int j = a; j >= num[i][1]; j --) {
                if(dp[i - 1][j - num[i][1]]) {
                    dp[i][j] += dp[i - 1][j - num[i][1]];
                }
            }
        }

        if(dp[cnt][a] != 1) {
            cout << "no" << endl;
        } else {
            bool ans[N][2] = {false};
            for(int i = cnt, j = a; i >= 1; i --) {
                if(dp[i - 1][j - num[i][0]] == 1) {
                    ans[i][0] = true;
                    j -= num[i][0];
                }
                else if(dp[i - 1][j - num[i][1]] == 1) {
                    ans[i][1] = true;
                    j -= num[i][1];
                }
            }
            for(int i = 1; i <= tot; i ++) {
                if(ans[rtid[get(i)]][d[i]]) cout << i << endl;
            }
            cout << "end" << endl;
        }
    }
    return 0;
}

/*

  Rejoicing in hope, patient in tribulation.

7 4 2
2 3 no
3 4 no
4 3 no
5 5 yes
5 3 no
5 5 yes
1 5 yes

1
2
4
5
end

0 3 0
0 0 3
0 10 20
2 10 0
1 2 yes
2 2 yes
2 0 10
1 2 yes
2 2 yes
2 5 5
1 2 yes
2 2 yes

1
2
3
end
end
no
1
2
3
4
5
6
7
8
9
10
end
end
no

*/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值