poj 1417 - True Liars(并查集+背包)

题意:

    题目中告诉两种人,一种只说真话,一种只说假话。然后告诉n个描述,每个描述是说a说b是说真话的人(yes)或者是说假话的人(no),最后问是否能判断哪些人是只说真话的那类人。

思路:

    根据题意可知:其中好人说真话,坏人说假话这点很重要。

    那么如果一个人说另一个人是好人,那么如果这个人是好人,说明 对方确实是好人,如果这个是坏人,说明这句话是假的,对方也是坏人。

    如果一个人说另一个人是坏人,那么如果这个人是好人,说明对方是坏人,如果这个是坏人,说明 对方是好人。

    也就是如果条件是yes说明这两个是相同集合的,否则是两个不同的集合

        然后我们通过并查集把有关系的一些人合并到一个集合内。

    然后问题就转化为:有n个集合,每个集合都有a, b连个数字,现在要求n个集合中各跳出一个数(a或者b),使得他们之和等于n1(说真话的人数)。

    然后用背包来解决这个问题,但答案要求输出所有说真话的人的编号,这就麻烦点了,我们要记录每个集合中的人的状态,还要记录dp的路径。具体方法见代码

代码如下:

const int M = 605;
int p[M], flag[M], dp[M][M/2], left[M][M/2];
int find(int x)
{
    int tmp = p[x];
    p[x] = (p[x] == x ? x : find(p[x]));
    flag[x] = flag[tmp]^flag[x];
    return p[x];
}
int main()
{
    int n, p1, p2, a, b, o;
    char tmp[10];
    while(scanf("%d%d%d", &n, &p1, &p2) && n+p1+p2)
    {
        for(int i = 1; i <= p1+p2; ++i) p[i] = i;
        for(int i = 0; i < n; ++i)
        {
            scanf("%d%d %s", &a, &b, tmp);
            o = strcmp("yes", tmp)==0?0:1;
            int x = find(a);
            int y = find(b);
            if(x != y)
            {
                p[x] = y;
                flag[x] = flag[a]^flag[b]^o;
            }
        }
        vector<int>A[M];
        vector<int>B[M];
        int vis[M], cnt = 1;
        memset(vis,0,sizeof(vis));
        for(int i = 1; i <= p1+p2; ++i)
        {
            if(!vis[i])
            {
                int f = find(i);
                for(int j = i; j <= p1+p2; ++j)
                    if(find(j)==f)
                    {
                        vis[j] = 1;
                        flag[j]==0?A[cnt].push_back(j):B[cnt].push_back(j);
                    }
                ++cnt;
            }
        }
        memset(dp,0,sizeof(dp));
        memset(left,0,sizeof(left));
        dp[0][0] = 1;
        for(int i = 1; i < cnt; ++i)
        {
            int la = A[i].size();
            int lb = B[i].size();
            for(int j = p1; j >= la||j >= lb; --j)
            {
                if(j>=la && dp[i-1][j-la]) dp[i][j] += dp[i-1][j-la], left[i][j] = j-la;
                if(j>=lb && dp[i-1][j-lb]) dp[i][j] += dp[i-1][j-lb], left[i][j] = j-lb;
            }
        }
        vector<int>ans;
        if(dp[cnt-1][p1]!=1) printf("no\n");
        else
        {
            int tt = p1;
            for(int i = cnt-1; i >= 1; --i)
            {
                int dt = tt-left[i][tt];
                if(dt==(int)A[i].size())
                    for(int j = 0; j < (int)A[i].size(); ++j) ans.push_back(A[i][j]);
                else
                    for(int j = 0; j < (int)B[i].size(); ++j) ans.push_back(B[i][j]);
                tt = left[i][tt];
            }
            sort(ans.begin(), ans.end());
            for(int i = 0; i < (int)ans.size(); ++i) printf("%d\n", ans[i]);
            printf("end\n");
        }
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值