侦探推理

来源:NOIP2003提高组 https://ac.nowcoder.com/acm/contest/251/B

 

 

明明同学最近迷上了侦探漫画《柯南》并沉醉于推理游戏之中,于是他召集了一群同学玩推理游戏。游戏的内容是这样的,明明的同学们先商量好由其中的一个人充当罪犯(在明明不知情的情况下),明明的任务就是找出这个罪犯。接着,明明逐个询问每一个同学,被询问者可能会说:

证词中出现的其他话,都不列入逻辑推理的内容。

明明所知道的是,他的同学中有N个人始终说假话,其余的人始终说真。

现在,明明需要你帮助他从他同学的话中推断出谁是真正的凶手,请记住,凶手只有一个!

 

输入描述:
输入由若干行组成,第一行有二个整数,M(1≤M≤20)、N(1≤N≤M)和P(1≤P≤100);M是参加游戏的明明的同学数,N是其中始终说谎的人数,P是证言的总数。接下来M行,每行是明明的一个同学的名字(英文字母组成,没有主格,全部大写)。
往后有P行,每行开始是某个同学的名宇,紧跟着一个冒号和一个空格,后面是一句证词,符合前表中所列格式。证词每行不会超过250个字符。
输入中不会出现连续的两个空格,而且每行开头和结尾也没有空格。



输出描述:
如果你的程序能确定谁是罪犯,则输出他的名字;如果程序判断出不止一个人可能是罪犯,则输出 Cannot Determine;如果程序判断出没有人可能成为罪犯,则输出 Impossible。
示例1

输入

3 1 5
MIKE
CHARLES
KATE
MIKE: I am guilty.
MIKE: Today is Sunday.
CHARLES: MIKE is guilty.
KATE: I am guilty.
KATE: How are you??

输出

MIKE

 


 

 

算法知识点: 枚举,模拟,字符串处理

复杂度:O(7M^2P)

解题思路:

对于比较繁琐的模拟题,写代码的时候建议尽可能模块化。
依次枚举每个同学是否可能是凶手。最终结果有三种:
可能的凶手只有一个,输出凶手名字;
可能的凶手多于一个,输出"Cannot Determine";
任何一个同学都不可能是凶手,输出"Impossible";
在枚举每个同学是凶手的过程中,依次枚举今天是星期几,然后判断说谎的同学是否有可能有 n 个。

这里有两点需要注意:
每个同学需要始终如一,如果出现某个同学既说真话又说假话,那么当前枚举的情况不合法;
每个句子有三种情况:真话、假话、废话。最终只要 假话数量 ≤n≤ 假话数量 + 废话数量,那么说谎的同学就有可能有 n 个;

时间复杂度分析:

我们会依次枚举凶手、星期几以及句子,对于每个句子在判断是谁说的时会再循环一遍所有同学,所以总时间复杂度是 O(7M^2P),其中 M 是总人数,P 是总句子数。

 

 

  1 #include <cstring>
  2 #include <iostream>
  3 #include <algorithm>
  4 using namespace std;
  5 const int N = 110,
  6     M = 30;
  7  
  8 int m, n, P;
  9 string sentence[N], name[M];
 10 string weekday[7] = {
 11     "Monday",
 12     "Tuesday",
 13     "Wednesday",
 14     "Thursday",
 15     "Friday",
 16     "Saturday",
 17     "Sunday"
 18 };
 19 int ch[M];
 20  
 21 pair <int, string> get(string str) // 对于每句话,剥离出说话者和说的话
 22 {
 23     int t = str.find(':');
 24     string person = str.substr(0, t);
 25     string sent = str.substr(t + 2);
 26  
 27     for (int i = 0; i < m; i++)
 28         if (name[i] == person)
 29             return {
 30                 i,
 31                 sent
 32             };
 33 }
 34  
 35 bool endwith(string s1, string s2) // 判断s1是否以s2结尾
 36 {
 37     int i = s1.find(s2);
 38     if (s1.size() - i == s2.size()) return true;
 39 }
 40  
 41 int judge(int bad_man, int p, int day, string sent) // 判断每句话的真假,真话返回0,假话返回1,无意义的话返回2
 42 {
 43     if (sent == "I am guilty.")
 44     {
 45         if (p == bad_man) return 0;
 46         return 1;
 47     }
 48     if (sent == "I am not guilty.")
 49     {
 50         if (p != bad_man) return 0;
 51         return 1;
 52     }
 53     if (endwith(sent, " is guilty."))
 54     {
 55         int t = sent.find(" is guilty."), now = -1;
 56         for (int i = 0; i < m; i++)
 57             if (sent.substr(0, t) == name[i])
 58             {
 59                 now = i;
 60                 break;
 61             }
 62         if (now == -1) return 2;
 63  
 64         if (now == bad_man) return 0;
 65         return 1;
 66     }
 67     if (endwith(sent, " is not guilty."))
 68     {
 69         int t = sent.find(" is not guilty."), now = -1;
 70         for (int i = 0; i < m; i++)
 71             if (sent.substr(0, t) == name[i])
 72             {
 73                 now = i;
 74                 break;
 75             }
 76         if (now == -1) return 2;
 77  
 78         if (now != bad_man) return 0;
 79         return 1;
 80     }
 81     if (sent.substr(0, 9) == "Today is ")
 82     {
 83         int now = -1;
 84         for (int i = 0; i < 7; i++)
 85             if (sent.substr(9) == weekday[i] + ".")
 86             {
 87                 now = i;
 88                 break;
 89             }
 90  
 91         if (now == -1) return 2;
 92  
 93         if (now == day) return 0;
 94         return 1;
 95     }
 96  
 97     return 2;
 98 }
 99  
100 bool check(int bad_man, int day) // 判断凶手是bad_man,今天是day的情况下,时候可能有n个同学说谎
101 {
102     memset(ch, -1, sizeof ch);
103  
104     for (int i = 0; i < P; i++)
105     {
106         auto ret = get(sentence[i]);
107         int p = ret.first;
108         string sent = ret.second;
109  
110         int t = judge(bad_man, p, day, sent);
111  
112         if (!t) // 真话
113         {
114             if (ch[p] == -1) ch[p] = 0;
115             else if (ch[p] == 1) return false;
116         }
117         else if (t == 1) // 假话
118         {
119             if (ch[p] == -1) ch[p] = 1;
120             else if (ch[p] == 0) return false;
121         }
122     }
123  
124     int c1 = 0, c2 = 0;
125     for (int i = 0; i < m; i++)
126         if (ch[i] == 1)
127             c1++;
128         else if (ch[i] == -1)
129         c2++;
130  
131     return c1 <= n && c1 + c2 >= n;
132 }
133  
134 int main()
135 {
136     cin >> m >> n >> P;
137     for (int i = 0; i < m; i++) cin >> name[i];
138  
139     getline(cin, sentence[0]);
140     for (int i = 0; i < P; i++) getline(cin, sentence[i]);
141  
142     int cnt = 0, p;
143     for (int i = 0; i < m; i++)
144     {
145         for (int day = 0; day < 7; day++)
146             if (check(i, day))
147             {
148                 p = i;
149                 cnt++;
150                 break;
151             }
152     }
153  
154     if (!cnt) puts("Impossible");
155     else if (cnt == 1) cout << name[p] << endl;
156     else puts("Cannot Determine");
157  
158     return 0;
159 }

 

 

 1 #include<iostream> 
 2 #include<cstring>
 3 #include<string>
 4 using namespace std;
 5 int n,m,p,fake[21],err,w[200],nx;
 6 string name[100],say[200];
 7 string day[10]={"233","Today is Sunday.","Today is Monday.","Today is Tuesday.","Today is Wednesday.","Today is Thursday.","Today is Friday.","Today is Saturday.",};
 8 void set(int who,int yx){
 9     //err是错误标记,如果一个人既说真话又说假话显然是不吼滴 
10     if(fake[who]&&fake[who]!=yx)err=1;
11     else fake[who]=yx;
12 }
13 int main(){
14     cin>>m>>n>>p;
15     for(int i=1;i<=m;i++)
16         cin>>name[i];
17     for(int i=1;i<=p;i++){
18         string nm;
19         cin>>nm;//输入这句话是谁说的 
20         nm.erase(nm.end()-1);//把冒号搞掉 
21  
22         for(int j=1;j<=m;j++)
23         if(name[j]==nm)w[i]=j; //判断这句话是谁说的
24  
25         getline(cin,say[i]);
26         say[i].erase(say[i].begin());//把空格搞掉 
27     //  say[i].erase(say[i].end()-1);
28     //把换行符去掉,上一行注释如果不去掉自己电脑能过,去掉评测能过。。。 
29     }
30     for(int td=1;td<=7;td++)//枚举今天是星期几 
31     for(int px=1;px<=m;px++){//枚举罪犯是谁 
32  
33         //清除标记 
34         err=0;
35         memset(fake,0,sizeof(fake)); 
36  
37         for(int i=1;i<=p;i++){//依次判断每句话 
38             int who=w[i];
39  
40             if(say[i]=="I am guilty.")set(who,px==who?1:-1);
41             //如果一个人是罪犯,而且说自己是罪犯,那他说的就是真话,否则就是假话 
42  
43             if(say[i]=="I am not guilty.")set(who,px!=who?1:-1);
44             //如果一个人不是罪犯,而且说自己不是罪犯,那他说的就是真话,否则就是假话 
45  
46             for(int j=1;j<=7;j++)
47             if(say[i]==day[j])set(who,j==td?1:-1);
48             //如果一个人说今天是星期几,说对了就是真话,说错了就是假话 
49             for(int j=1;j<=m;j++){
50                 if(say[i]==name[j]+" is guilty.")set(who,j==px?1:-1);
51                 if(say[i]==name[j]+" is not guilty.")set(who,j!=px?1:-1);
52                 //显然说对了就是真话,否则就是假话 
53             }
54         }
55         int cnt=0,ppp=0;//cnt:说假话的人数,ppp:不确定的人数 
56         for(int i=1;i<=m;i++){
57             if(fake[i]==-1)cnt++;
58             if(fake[i]==0)ppp++;
59         }
60         if(!err&&cnt<=n&&cnt+ppp>=n)
61         // 如果cnt<=n<=cnt+ppp,就说明这个假设合理 
62         //如果出现Impossible的情况,怎么枚举都会err,这里直接跳过就行了 
63             if(nx&&nx!=px){
64                 //出现了两个合理的罪犯 
65                 cout<<"Cannot Determine";
66                 return 0;
67             }else nx=px;
68     }
69     if(!nx)cout<<"Impossible"; 
70     else cout<<name[nx];
71 }

 

 

 

posted @ 2019-07-23 11:14  jiamian22  阅读(431)  评论(0编辑  收藏  举报