��վ�ܷ�������

题解:LUOGU P1039 侦探推理

注意洛谷和codevs是Linux的,Vijos是Windows的,所以洛谷的换行符是’/r’,所以getline(),gets(),getchar(),都可能有一定问题

于是这个可以解决一切

    char ch=getchar();
    while(ch==10)ch=getchar();getchar();

解决’\r’,多余空格,换行符一系列问题

来自julao

很棒棒!!!的操作 妈妈再也不用担心我卡读入了!!

循环很多注意

思路

1. 读入处理

首先一看就知道是大模拟,所以萌新瑟瑟发抖想了很久要不要写
仔细一想好像略有思路

首先解决读入处理,我选择的是用二维数组存储每人说了些什么,这样记完之后就很方便了,这大概是核心操作,具体看代码,我用的是打表strcmp,截取部分串来判定

2. 然后如何找出犯人

我是先枚举犯人和星期几,这样判定说假说真话会方便很多,依据犯人来看是否说真话,如我的guilty数组;再看是否与今天星期几相符,如我的day数组。
然后直接统计是否符合题目就行

坑点

这么看来这题没啥难度吗!
然而看看讨论,发现事情并没有那么简单
首先有一个人又说自己有罪又说自己没罪。。。。
我好像直接判定impossible了

还有一位叫GUILTY的同志以及一位叫I的同志
还有叫MONDAY-SUNDAY的同志

以及很多很多有意思的东西,大家可以看看讨论
再就是有一句话没说的,可以看做说假话或者说真话,随意处理 这是就是我的zero,在统计时需要枚举一下可能性
还有互相矛盾的,如在Monday B是犯人的假设下:

A:Today is Tuesday.
A: C is not guilty.

虽在星期条件下A是假,但第二句又是真话,故又假又真
矛盾,不成立。
血的教训,如Test3 Test8

代码

    1. 读入处理(最核心最长的部分)
void update()
{
    int len=strlen(s),cnt=0,cnt2=0;
    int lena;//第几位名字结束
    int ren=0,ren2=0;//
    char t[30],name2[300];//因为我用的都是strcmp判定需要提取部分子串来直接判断
    memset(t,0,sizeof(t));//论memset的重要性emmmm
    memset(name2,0,sizeof(name2));
    for(int i=0;i<len;i++)
    {
        if(s[i]==':'){lena=i-1;break;}
        else name2[cnt2++]=s[i];//子串截取一
    }
    for(int i=1;i<=m;i++)//判断是谁说的
    {
        int lenb=strlen(name[i]),flag=0;
        if(lenb!=lena+1)continue;
        else
        {
           if(!strcmp(name2,name[i]))
              {ren=i;break;}
        }
    }
    if(!ren)return;//若没判定到,直接退出(无用信息
    for(int i=lena+3;i<len;i++)
        if(s[i]!='\r')t[cnt++]=s[i];//截取二,注意是'\r'不是'\n'
    int ff=0;
    for(int i=0;i<=8;i++)
    {
        if(!strcmp(t,ch[i]))
           {
               ff=1;
               if(i==0)guilty[ren][ren]=1;//记录此人说谁有无罪,这里我的1表示有罪,2为无罪
               else if(i==1)guilty[ren][ren]=2;
               else day[ren][i-1]=1;//记录此人说几天星期几
               break;
           }

    }
    if(!ff)//继续进一步判定
    {
        int lenx;
        char t2[300],coc=0,t3[20],tot=0;
        memset(t2,0,sizeof(t2));
        memset(t3,0,sizeof(t3));
        for(int i=0;i<strlen(t);i++)
        {
            if(t[i]==' '){lenx=i-1;break;}
            else t2[coc++]=t[i];//截取三
        }
        for(int i=1;i<=m;i++)
        {
            if(lenx+1!=strlen(name[i]))continue;
            else if(!strcmp(t2,name[i])){ren2=i;break;}
        }
        if(!ren2)return;//若没判定到,直接退出(无用信息
        for(int i=lenx+2;i<strlen(t);i++)
            if(t[i]!='\r')t3[tot++]=t[i];//截取四 注意是'\r'不是'\n'
        for(int i=0;i<2;i++)
        {
            if(!strcmp(t3,ch2[i]))
            {
                if(i==0)guilty[ren][ren2]=1;
                else guilty[ren][ren2]=2;
                break;
            }
        }
        memset(t2,0,sizeof(t2));
        memset(t3,0,sizeof(t3));

    }

}
  • 找出犯人(坑点最多)
int check(int peo,int today)//枚举罪犯 星期几
{
    memset(jia,0,sizeof(jia));
    int fake=0;//一定说假话的人
    int zero=0;//没说过一句话的人,可以是真是假,随意欺负
    for(int i=1;i<=m;i++)
    {
       int g=0,gg=0;//判定是否没说话
       if(guilty[i][peo]==2)jia[i]=1;//承认罪犯无罪,说假话
       if(guilty[i][peo]==1)zhen[i]=1;//有罪,真话
       //好像有又说自己有罪又说自己无罪的人。。。。。。
       if(jia[i])continue;
       for(int j=1;j<=m;j++)
           if(guilty[i][j]==1&&j!=peo){jia[i]=1;break;}//若有指证别人是犯人的,假
        for(int j=1;j<=m;j++)
           if(guilty[i][j]==2&&j!=peo){zhen[i]=1;break;}//若有指证别人不是犯人的,真
        //关于问啥判两遍,
       if(jia[i])continue;
       for(int j=1;j<=7;j++)
           if(day[i][j]==1&&j!=today){jia[i]=1;break;}//不符合今天星期几的,假
       for(int j=1;j<=m;j++)
       if(guilty[i][j]==1||guilty[i][j]==2){g=1;break;}//判定是否没说话
       for(int j=1;j<=7;j++)
       if(day[i][j]==1){gg=1;break;}//判定是否没说话
       if(zhen[i]==1&&jia[i]==1)return 0;
       if(!gg&&!g)zero++;//没说话,zero++

    }

    for(int i=1;i<=m;i++)
        if(jia[i])fake++;
    for(int i=0;i<=zero;i++)
    {
        if(fake+i==n)return 1;//没说过一句话的人,可以是真是假,
    }
   return 0;
}
  • 主函数
int main()
{
    scanf("%d%d%d",&m,&n,&p);
    for(int i=1;i<=m;i++)
        scanf("%s",name[i]);
     char ch=getchar();//因为luogu读入很坑,所以这两句很有必要,解决'\r',多余空格,换行符一系列问题
    while(ch==10)ch=getchar();getchar();
    for(int i=1;i<=p;i++)
    {
       gets(s);
        update(i);
    }
    //以上操作后,我们已经记录了谁说今天星期几,谁说谁有罪以及筛掉无用信息
    for(int i=1;i<=m;i++)//枚举谁是罪犯
    {
        for(int j=1;j<=7;j++)//枚举今天星期几
        {
            if(check(i,j))
            {

                if(tot1>1&&i!=ans2[cocc]){printf("Cannot Determine");return 0;}//若有多个,输出
                if(i!=ans2[cocc])//第一次记录答案
                {
                     tot1++;
                    strcpy(ans,name[i]);
                    cocc++;
                    ans2[cocc]=i;
                }
            }
        }
    }
    if(!tot1)printf("Impossible");//没查到答案
    else printf("%s",ans);

     return 0;
}

  • 总代码
#include <bits/stdc++.h>
using namespace std;
char ch[20][20]=
      {
          "I am guilty.",//0
          "I am not guilty.",//1
          "Today is Monday.",//2
          "Today is Tuesday.",//3
          "Today is Wednesday.",//4
          "Today is Thursday.",//5
          "Today is Friday.",//6
          "Today is Saturday.",//7
          "Today is Sunday.",//8
      };//表一
char ch2[10][20]=
     {
         "is guilty.",
         "is not guilty.",

     };//表二
int sum,guilty[30][30];//记录谁说谁有,无罪
int day[30][9];//记录谁说今天星期几
int n,m,p;
char name[30][500],s[10001],ans[300];//名字,输入的串,答案记录
int jia[40],zhen[40];//真假数组,后面一个用处不是很大
void update()
{
    int len=strlen(s),cnt=0,cnt2=0;
    int lena;//第几位名字结束
    int ren=0,ren2=0;//
    char t[30],name2[300];//因为我用的都是strcmp判定需要提取部分子串来直接判断
    memset(t,0,sizeof(t));//论memset的重要性emmmm
    memset(name2,0,sizeof(name2));
    for(int i=0;i<len;i++)
    {
        if(s[i]==':'){lena=i-1;break;}
        else name2[cnt2++]=s[i];//子串截取一
    }
    for(int i=1;i<=m;i++)//判断是谁说的
    {
        int lenb=strlen(name[i]),flag=0;
        if(lenb!=lena+1)continue;
        else
        {
           if(!strcmp(name2,name[i]))
              {ren=i;break;}
        }
    }
    if(!ren)return;//若没判定到,直接退出(无用信息
    for(int i=lena+3;i<len;i++)
        if(s[i]!='\r')t[cnt++]=s[i];//截取二,注意是'\r'不是'\n'
    int ff=0;
    for(int i=0;i<=8;i++)
    {
        if(!strcmp(t,ch[i]))
           {
               ff=1;
               if(i==0)guilty[ren][ren]=1;//记录此人说谁有无罪,这里我的1表示有罪,2为无罪
               else if(i==1)guilty[ren][ren]=2;
               else day[ren][i-1]=1;//记录此人说几天星期几
               break;
           }

    }
    if(!ff)//继续进一步判定
    {
        int lenx;
        char t2[300],coc=0,t3[20],tot=0;
        memset(t2,0,sizeof(t2));
        memset(t3,0,sizeof(t3));
        for(int i=0;i<strlen(t);i++)
        {
            if(t[i]==' '){lenx=i-1;break;}
            else t2[coc++]=t[i];//截取三
        }
        for(int i=1;i<=m;i++)
        {
            if(lenx+1!=strlen(name[i]))continue;
            else if(!strcmp(t2,name[i])){ren2=i;break;}
        }
        if(!ren2)return;//若没判定到,直接退出(无用信息
        for(int i=lenx+2;i<strlen(t);i++)
            if(t[i]!='\r')t3[tot++]=t[i];//截取四 注意是'\r'不是'\n'
        for(int i=0;i<2;i++)
        {
            if(!strcmp(t3,ch2[i]))
            {
                if(i==0)guilty[ren][ren2]=1;
                else guilty[ren][ren2]=2;
                break;
            }
        }
        memset(t2,0,sizeof(t2));
        memset(t3,0,sizeof(t3));

    }
}
int check(int peo,int today)//枚举罪犯 星期几
{
    memset(jia,0,sizeof(jia));
    int fake=0;//一定说假话的人
    int zero=0;//没说过一句话的人,可以是真是假,随意欺负
    for(int i=1;i<=m;i++)
    {
       int g=0,gg=0;//判定是否没说话
       if(guilty[i][peo]==2)jia[i]=1;//承认罪犯无罪,说假话
       if(guilty[i][peo]==1)zhen[i]=1;//有罪,真话
       //好像有又说自己有罪又说自己无罪的人。。。。。。
       if(jia[i])continue;
       for(int j=1;j<=m;j++)
           if(guilty[i][j]==1&&j!=peo){jia[i]=1;break;}//若有指证别人是犯人的,假
        for(int j=1;j<=m;j++)
           if(guilty[i][j]==2&&j!=peo){zhen[i]=1;break;}//若有指证别人不是犯人的,真
        //关于问啥判两遍,
       if(jia[i])continue;
       for(int j=1;j<=7;j++)
           if(day[i][j]==1&&j!=today){jia[i]=1;break;}//不符合今天星期几的,假
       for(int j=1;j<=m;j++)
       if(guilty[i][j]==1||guilty[i][j]==2){g=1;break;}//判定是否没说话
       for(int j=1;j<=7;j++)
       if(day[i][j]==1){gg=1;break;}//判定是否没说话
       if(zhen[i]==1&&jia[i]==1)return 0;
       if(!gg&&!g)zero++;//没说话,zero++

    }

    for(int i=1;i<=m;i++)
        if(jia[i])fake++;
    for(int i=0;i<=zero;i++)
    {
        if(fake+i==n)return 1;//没说过一句话的人,可以是真是假,
    }
   return 0;
}
int tot1=0,cocc=0;
int ans2[40];
int main()
{
    scanf("%d%d%d",&m,&n,&p);
    for(int i=1;i<=m;i++)
        scanf("%s",name[i]);
     char ch=getchar();//因为luogu读入很坑,所以这两句很有必要,解决'\r',多余空格,换行符一系列问题
    while(ch==10)ch=getchar();getchar();
    for(int i=1;i<=p;i++)
    {
       gets(s);
        update();
    }
    //以上操作后,我们已经记录了谁说今天星期几,谁说谁有罪以及筛掉无用信息
    for(int i=1;i<=m;i++)//枚举谁是罪犯
    {
        for(int j=1;j<=7;j++)//枚举今天星期几
        {
            if(check(i,j))
            {

                if(tot1>1&&i!=ans2[cocc]){printf("Cannot Determine");return 0;}//若有多个,输出
                if(i!=ans2[cocc])//第一次记录答案
                {
                     tot1++;
                    strcpy(ans,name[i]);
                    cocc++;
                    ans2[cocc]=i;
                }
            }
        }
    }
    if(!tot1)printf("Impossible");//没查到答案
    else printf("%s",ans);

     return 0;
}

总共172行,函数有大概15个循环

历经千辛万苦终于AC

总之还是比较考验码力和细心程度的题

可以码码练练手

感谢