poj 1417 True Liars

11 篇文章 1 订阅

题意:有p1个好人,有p2个坏人,给出n个信息,每个信息表示编号为x的人和编号为y的人是否为同一集合,问是否能唯一确定p1中有哪些人。

思路:首先要做一个并查集,用rt[i]记录i与根节点的关系,0表示与根节点属于同一集合,1表示与根节点所在集合不同,这样把所有信息处理完以后,会得到若干个集合,每个集合中只能获得不同集合的人数,但是不能知道某个集合是否是好人。这样的话还要做一个dp,用dp[i][j]表示前i个集合能组合出j个人数的种数,跟背包差不多的东西,做完以后判断一下dp[k][p1]是否为1(k为集合个数)就行了,由于还要输出好人的编号,因此还要回溯路径,找出某一集合中的元素跟根节点相同的是好人还是不同的是好人就行了,这东西写起来感觉蛮麻烦的,幸好没调多长时间就过了,不然要烦死。

 

代码:

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#define inf 0x3f3f3f3f
#define Inf 0x3FFFFFFFFFFFFFFFLL
#define eps 1e-9
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
const int maxn=600+10;
int parents[maxn],rt[maxn];
int con[maxn],cnt[maxn],diff[maxn],ron[maxn];
int ans[maxn];
int dp[maxn][maxn];
int Find(int x)
{
    if(x==parents[x]) return x;
    int tmp=parents[x];
    Find(parents[x]);
    rt[x]=rt[tmp]^rt[x];
    parents[x]=parents[tmp];
    return parents[x];
}
void Uion(int x,int y,int v)
{
    int a=Find(x);
    int b=Find(y);
    if(a!=b)
    {
        if(v)
        {
            rt[b]=rt[x]^rt[y]^v;
            parents[b]=a;
        }
        else
        {
            rt[b]=rt[x]^rt[y];
            parents[b]=a;
        }
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int n,p1,p2;
    int x,y;
    char str[5];
    while(~scanf("%d%d%d",&n,&p1,&p2))
    {
        if(n==0&&p1==0&&p2==0) break;
        int m=p1+p2;
        for(int i=1;i<=m;++i) parents[i]=i,rt[i]=0;
        memset(con,0,sizeof(con));
        memset(cnt,0,sizeof(cnt));
        memset(diff,0,sizeof(diff));
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;++i)
        {
            scanf("%d%d%s",&x,&y,str);
            if(str[0]=='y') Uion(x,y,0);
            else Uion(x,y,1);
        }
        for(int i=1;i<=m;++i) Find(i);
        int k=0,pa;
        for(int i=1;i<=m;++i)
        {
            pa=parents[i];
            if(con[pa]==0)
            {
                con[pa]=++k;
                ron[k]=pa;
            }
            cnt[con[pa]]++;
            if(rt[i]) diff[con[pa]]++;
        }
        dp[0][0]=1;
        bool flag=true;
        for(int i=1;i<=k;++i)
        {
            x=diff[i];y=cnt[i]-diff[i];
            if(x==y) flag=false;
            for(int j=m;j>=x;--j)
              dp[i][j]+=dp[i-1][j-x];
            for(int j=m;j>=y;--j)
              dp[i][j]+=dp[i-1][j-y];
        }
        if(dp[k][p1]!=1||!flag)
          printf("no\n");
        else
        {
            int now=p1;
            for(int i=k;i>=1;--i)
            {
                x=diff[i];y=cnt[i]-diff[i];
                if(dp[i-1][now-x]==1)
                {
                    ans[ron[i]]=1;
                    now-=x;
                }
                else if(dp[i-1][now-y]==1)
                {
                    ans[ron[i]]=0;
                    now-=y;
                }
            }
            for(int i=1;i<=m;++i)
            {
                pa=parents[i];
                if(ans[pa]==rt[i])
                 printf("%d\n",i);
            }
            printf("end\n");
        }
    }
    return 0;
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值