• 0x40数据结构进阶(练习)3:真正的骗子(题解)


    题意

    链接

    【题意】
     一个岛上存在着两种居民,一种是天神,一种是恶魔。
     天神永远都不会说假话,而恶魔永远都不会说真话。
     岛上的每一个成员都有一个整数编号(类似于身份证号,用以区分每个成员)。
     现在你拥有n次提问的机会,但是问题的内容只能是向其中一个居民询问另一个居民是否是天神,请你根据收集的回答判断各个居民的身份。 
    【输入格式】
     输入包含多组测试用例。
     每组测试用例的第一行包含三个非负整数n,p1,p2,其中n是你可以提问的总次数,p1是天神的总数量,p2是恶魔的总数量。
     接下来n行每行包含两个整数xi,yi以及一个字符串ai,其中xi,yi是岛上居民的编号,你将向编号为xi的居民询问编号为yi的居民是否是天神,
    ai是他的回答,如果ai为“yes”,表示他回答你“是”,如果ai为“no”,表示他回答你“不是”。
    xi,yi可能相同,表示你问的是那个人自己是否为天神。
     当输入为占据一行的“0 0 0”时,表示输入终止。 
    【输出格式】
     对于每组测试用例,如果询问得到的信息足以使你判断每个居民的身份,则将所有天神的编号输出,每个编号占一行,在输出结束后,在另起一行输出“end”,表示该用例输出结束。
     如果得到的信息不足以判断每个居民的身份,则输出“no”,输出同样占一行。 
    【数据范围】
    1≤xi,yi≤p1+p2,
     1≤n<1000,1≤p1,p2<300。 
    【输入样例】
    2 1 1
     1 2 no
     2 1 no
     3 2 1
     1 1 yes
     2 2 yes
     3 3 yes
     2 2 1
     1 2 yes
     2 3 no
     5 4 3
     1 2 yes
     1 3 no
     4 5 yes
     5 6 yes
     6 7 no
     0 0 0
    【输出样例】
    no
     no
     1
     2
     end
     3
     4
     5
     6
     end 
    

    讲解

    我们将这个列一列,我们发现问自己是不是天神永远说的是yes,而且这道题目不存在违反解的情况,那么我们就可以先处理出每个人之间的关系。

    如果一个人说另外一个人是天神,那么说明这两个人是同种身份,如果说不是天神,那么他们是不同身份,这个列一列就知道了。

    那么我们很快发现不就是带权并查集吗,不同的不同就是相同。

    那么我们就可以处理出对于每个集合与祖先不同的有多少个数字,就可以推出每个集合内两种不同的身份分别有多少人,对于第(i)个集合中两种不同的身份人数为(a_i,b_i),但是我们并不知道(a_i)是哪种身份的,可以(a_i)个人天神,(b_i)个人恶魔,反之也可以。

    但是我们又发现他给定了天神数量,那么问题就变成了现在有(k)组数字,每组有两个数字,让你在每组选择一个数字,使得这个数字等于(p1),问方案数。

    方案数为(1)输出方案。

    一种很简单的思路是设(f[i][j])表示到第(i)组已经有(j)个天神了,类似背包一样瞎跑跑。但是我真的没想出来

    于是我用了另外一种方法就是对于每组我们都假设取了(max(a_i,b_i)),那么就有(now)个天神了,很明显我们需要减去(max(a_i,b_i)-min(a_i,b_i)),那么我们就可以用每组的(max(a_i,b_i)-min(a_i,b_i))去填(now-q1),变成经典的01背包,然后计算方案数,很明显,当(a_i=b_i)时,肯定方案数大于(1)

    这里我们把多种方案设为(2),否则方案不断积累会爆long long。

    时间复杂度(O(n^2))

    当然这种做法在随机数据下会特别的快。

    //3ms猛如虎
    #include<cstdio>
    #include<cstring>
    #define  N  1100
    using  namespace  std;
    int  fa[N],val[N]/*到根的权值*/,n,m,q,p;
    int  cnt[N],siz[N],bbk[N];//表示当自己为父亲的时候,子树内总共有多少数字和自己不一样。 
    int  findfa(int  x)
    {
        if(fa[x]==x)return  x;
        int  y=findfa(fa[x]);val[x]=val[x]*val[fa[x]];fa[x]=y;//表示的就是转移 
        return  fa[x];
    }
    bool  dp[N],bk[N];int  tot[N],pre[N]/*状态*/,use[N];
    int  main()
    {
        while(scanf("%d%d%d",&m,&q,&p)!=EOF)
        {
            if(m==0  &&  q==0  &&  p==0)break;
            //初始化 
            n=q+p;
            for(int  i=1;i<=n;i++)fa[i]=i,val[i]=siz[i]=1,cnt[i]=0,bk[i]=0,bbk[i]=1;//永远都是跟自己相等的 
            memset(dp,0,sizeof(dp));dp[0]=0;
            memset(tot,0,sizeof(tot));
            //
            for(int  i=1;i<=m;i++)
            {
                int  x,y,z;char  st[20];scanf("%d%d%s",&x,&y,st+1);
                z=(st[1]=='y'?1:-1);
                int  tx=findfa(x),ty=findfa(y);
                if(tx!=ty)fa[tx]=ty,val[tx]=z*val[x]*val[y],siz[ty]+=siz[tx];/*表示他和fa的关系*/
            }
            for(int  i=1;i<=n;i++)
            {
                int  x=findfa(i);//因为我们要算出每个联通块里面有多少个为-1的,同时计算出每个点的祖先。 
                cnt[x]+=(val[i]==-1);
            }
            int  now=0;
            for(int  i=1;i<=n;i++)
            {
                if(fa[i]==i)
                {
                    if(siz[i]-cnt[i]>cnt[i])cnt[i]=siz[i]-cnt[i],bbk[i]=-1/*表示现在的cnt是与fa相等的集合*/;
                    now+=cnt[i];
                }
            }
             
            int  k=now-q;dp[0]=1;tot[0]=1;
            for(int  i=1;i<=n;i++)
            {
                if(fa[i]==i)
                {
                    int  x=(cnt[i]<<1)-siz[i];
                    for(int  j=k;j>=x;j--)
                    {
                        if(dp[j-x]==1)
                        {
                            if(dp[j]==0)dp[j]=1,tot[j]=tot[j-x],pre[j]=j-x,use[j]=i;
                            else  tot[j]=2;//多种方案
                        }
                    }
                }
            }
            if(tot[k]>1)printf("no
    ");
            else
            {
                int  x=k;
                while(x)
                {
                    bk[use[x]]=1;//标记是哪个块选的是小的集合
                    x=pre[x];
                }
                for(int  i=1;i<=n;i++)
                {
                    if((bk[fa[i]]==1  &&  val[i]*bbk[fa[i]]==1)  ||  (bk[fa[i]]==0  &&  val[i]*bbk[fa[i]]==-1))printf("%d
    ",i);
                }
                printf("end
    ");
            }
        }
        return  0;
    }
    
    
  • 相关阅读:
    JMeter递增加压总结
    JMeter跨线程传参总结
    JMeter+Grafana+Influxdb可视化性能监控平台搭建总结
    MONyog入门总结
    MongoDB导出/导入操作
    测试工作中用到的MongoDB命令
    Go单元测试与报告
    Postwoman教程
    测试工作中用到的Redis命令
    redis-dump教程
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/11713626.html
Copyright © 2020-2023  润新知