[POJ 1417]true liars

题目传送-POJ1417

题意:

一共有\(p1\)个好人,\(p2\)个坏人.
现在有\(m\)句话(a,b,YES/NO)
表示\(a\)\(b\)是否是好人
好人都说真话,坏人都说假话
问方案是否唯一,并输出唯一的方案
\(p1,p2\le300,m\le1000\)

题解:

观察性质发现:当为NO时,ab好坏相反,否则相同
那么考虑并查集维护出敌对和友好关系
其中每一个团有一个敌对团,即两个团之间的好坏必然相反
那么把这些敌对的数量搞出来,背包一发就行了
路径输出不解释了

过程:

并查集记得把关系都建满了..

代码:

const int N=1210;
int p1,p2,n,m;
int fat[N<<1],sz[N<<1];
char s[20];
int father(int x) {return fat[x]==x ? x : fat[x]=father(fat[x]);}
inline void Union(int x,int y) {
    int fx=father(x),fy=father(y);
    if(fx!=fy) {fat[fx]=fy; sz[fy]+=sz[fx]; sz[fx]=0;}
}
int used[N];
pii a[N][2]; int tot=0;
int f[N][N],fr[N][N];
int cho[N],ind=0;
void Print(int dep,int j) {
    if(dep==0) return;
    int type=fr[dep][j];
    cho[++ind]=a[dep][type].S;
    Print(dep-1,j-a[dep][type].F);
}
int ans[N],res=0;
signed main() {
    while(scanf("%d %d %d",&m,&p1,&p2) && (m || p1 || p2)) {
        ind=tot=res=0; mem(used,0); bool fl=1;
        n=p1+p2; mem(sz,0); mem(f,0);
        for(int i=1;i<=n*2;i++) fat[i]=i;
        for(int i=1;i<=n;i++) sz[i]=1;
        for(int i=1;i<=m;i++) {
            int x,y; read(x); read(y); scanf("%s",s+1);
            Union(x+(s[1]=='y' ? 0 : n),y);
            Union(x,y+(s[1]=='y' ? 0 : n));
            if(s[1]=='y') Union(x+n,y+n);
        }
        for(int i=n+1;i<=2*n;i++) {
            int fx=father(i),fy=father(i-n);
            // printf("%d %d\n",fx,fy);
            if(fx==fy) {puts("no"); fl=0; break;}
            if(used[fx]) {assert(used[fy]); continue;}
            used[fx]=used[fy]=1;
            a[++tot][0]=mp(sz[fx],fx);
            a[tot][1]=mp(sz[fy],fy);
        }
        if(!fl) continue;
        f[0][0]=1;
        // printf("tot=%d\n",tot);
        // for(int i=1;i<=tot;i++) printf("%d %d\n",a[i][0].F,a[i][1].F);
        for(int i=1;i<=tot;i++) {
            int bd=a[i][0].F;
            for(int j=p1;j>=bd;j--)
                if(f[i-1][j-bd]) {
                    fr[i][j]=0;
                    f[i][j]+=f[i-1][j-bd];
                    if(f[i][j]>1) f[i][j]=2;
                }
            bd=a[i][1].F;
            for(int j=p1;j>=bd;j--)
                if(f[i-1][j-bd]) {
                    fr[i][j]=1;
                    f[i][j]+=f[i-1][j-bd];
                    if(f[i][j]>1) f[i][j]=2;
                }
        }
        if(f[tot][p1]==1) {
            Print(tot,p1);
            for(int i=1;i<=n;i++) {
                int anc=father(i);
                for(int j=1;j<=ind;j++)
                    if(anc==cho[j]) {ans[++res]=i; break;}
            }
            sort(ans+1,ans+res+1);
            for(int i=1;i<=res;i++)
                printf("%d\n",ans[i]);
            puts("end");
        } else puts("no");
    }
    return 0;
}

用时:1h

转载于:https://www.cnblogs.com/functionendless/p/9498182.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值