此题相当锻炼编程的基本功夫,参加Noip的新手不要错过!
1
/*
2
=========程序信息========
3
对应题目:noip2003提高组_侦探推理
4
使用语言:c++
5
使用编译器:dev c++
6
使用算法:穷举模拟
7
算法运行时间:O(p*C(m,n)) + O(m*C(m,n)) [其中C(m,n)是从m个数中取n个数的组合数]
8
作者:贵州大学05级 刘永辉
9
昵称:SDJL
10
编写时间:2008年8月
11
联系QQ:44561907
12
E-Mail:44561907@qq.com
13
获得更多文章请访问我的博客:www.cnblogs.com/sdjl
14
如果发现BUG或有写得不好的地方请发邮件告诉我:)
15
=========题目解析========
16
在做此题之前,我们需要搞清楚几点,这是我从测试数据中看出而从题目上不容易看出来的:
17
题目补充点一:如果有4个人a、b、c、d,当a、b说谎话时我们推出了c是罪犯,然后当a、c说谎话时我们又推出了c是罪犯,此时我们仅推出一个罪犯,而不是两个,因此输出为c,而不是输出Cannot Determine,注意不要重复计数。
18
题目补充点二:如果有4个人a、b、c、d,当我们推出a、b、c三人都不是罪犯而d未知时,那么d就是罪犯,这是从某些测试数据中发现的规则,题目没有明说。
19
题目补充点三:不会出现重名情况。
20
题目补充点四:注意“I am guilty.”、“Today is Monday.”等语句的后面均有一个“.”号。
21
22
在确定上面几点后我们来思考此题如何做,首先我想到的是用模拟方法,并且此程序使用的就是模拟方法,也就是说,我们用组合的方式从m个人中取出n个人假设这n个人说谎话,然后分别对这m个人所说的共p句话进行判断,如果p句话均两两不矛盾的话,那么就看看能不能正好找出一个罪犯,或者正好找出m-1个“好人”的话(“好人”指不是罪犯的人,下同),另一个便认为是罪犯。
23
同时注意分析,本质上有效的证言仅有4种类型,第一种是“谁是罪犯”,第二种是“谁不是罪犯”,第三种是“今天是星期几”,第四种是“今天不是星期几”,并且如果某个说谎者的证言类型为“谁是罪犯”,那么他的反证言“谁不是罪犯”一定成立。
24
无论在这n个人说谎的情况下能不能找出罪犯,都要从m个人中重新找出一组n个人的组合,再次进行判断(即寻找罪犯)。
25
直到所有说谎人的组合情况都考虑后再来看哪些人有可能是罪犯,如果超过两个,则输出Cannot Determine,如果正好有一个,则输出此人的名字,如果一个都没有,则输出Impossible。
26
*/
27
28
#include <cstdlib>
29
#include <iostream>
30
#include <fstream>
31
32
using namespace std;
33
const int maxn=20;//最多参与人数
34
const int maxTestimony=100;//最多证词数
35
36
//证词枚举类型,分别对应“I am guilty.”、“I am not guilty”、“Today is XXX”、“Today not is XXX”及其它废话。
37
enum TestimonyWordsType
38
{
39
IsGuilty,NotIsGuilty,TodayIs,TodayNotIs,NullWords
40
};
41
//星期枚举类型,分别从“星期一”对应到“星期日”,最后一个是“未知”,注意,枚举事实上是一个从0开始计数的整数,Monday事实上等于0,Tuesday等于1,Unknown等于7,因此后面代码出现诸如“isToday[Monday]=1”的代码时请不要惊讶,这表示今天是星期一
42
enum WeekType
43
{
44
Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday,Unknown
45
};
46
//证言结构体
47
struct TestimonyStruct
48
{
49
TestimonyWordsType Words;//证言的类型,IsGuilty、NotIsGuilty、TodayIs、TodayNotIs、NullWords五个之一
50
TestimonyWordsType Reverse;//证言的反类型,IsGuilty、NotIsGuilty互反,TodayIs、TodayNotIs互反,NullWords的反类型还是NullWords
51
52
int PeopleNum;//说话内容里面所涉及到的人的编号,比如说“SDJL is guilty.”那么就是SDJL此人的编号,此字段仅在Words属于IsGuilty、NotIsGuilty之一时有用
53
WeekType Week;//说话内容里面所涉及到的星期,比如说“Today is Monday.”那么Week就等于Monday,此字段仅在Words属于TodayIs、TodayNotIs之一时有用
54
55
int SpeakerNum;//说话者编号
56
};
57
//人结构体
58
struct PeopleStruct
59
{
60
string Name;//人名
61
int Num;//编号
62
};
63
64
PeopleStruct people[maxn];//人
65
int n;//说谎者人数
66
int m;//总人数
67
int p;//证言数,注意一个人可能有多句证言,或没有证言
68
int StoryPeople[maxn];//StoryPeople[3]=5表示第3个说谎者是people[5](从第0个开始计数),此全局变量仅用于保存组合算法的状态,其它地方不会用到
69
bool isStoryPeople[maxn];//isStoryPople[5]=true表示第5个人说的是谎话,此全局变量在组合算法中得到它的值,而在验证机中用来判断某人是否说谎话
70
TestimonyStruct testimony[maxTestimony];//证言
71
int guilty[maxn];//guilty[3]=2表示两次推出第3个人是罪犯,等于0表示不能推出此人是罪犯,这里我们不能简单的用一个整数来记录找出的罪犯数以及用一个字符串来记录罪犯的名字,这是因为要避免前面所提到的“题目补充点一”情况。
72
73
//这两个全局变量用于验证机
74
int peopleIsGuilty[maxn];//peopleIsGuilty[7]=1表示第7个人是罪犯,=-1表示不是罪犯,=0表示未知
75
int isToday[7];//isToday[3]=1 表示今天是星期四, isToday[6]=-1 表示今天不是星期日,别忘了是从0开始计数,等于0时表示未知,isToday数组中最多只能有一个为1
76
77
//根据人名得到此人的编号
78
int GetPeopleNum(string name)//运行时间:O(m)
79
{
80
for(int i=0; i<m; i++)
81
{
82
if(people[i].Name == name)
83
return i;
84
}
85
//这里应该抛出异常,表示给出的人名错误
86
}
87
88
//根据星期名称字符串得到星期枚举类型
89
WeekType GetWeekType(string weekString)//运行时间:O(1)
90
{
91
//字符串居然不能用于switch语句?这是我没有预料到的
92
WeekType returnWeekType;
93
if(weekString == "Monday.")
94
{
95
returnWeekType = Monday;
96
}
97
else if(weekString == "Tuesday.")
98
{
99
returnWeekType = Tuesday;
100
}
101
else if(weekString == "Wednesday.")
102
{
103
returnWeekType = Wednesday;
104
}
105
else if(weekString == "Thursday.")
106
{
107
returnWeekType = Thursday;
108
}
109
else if(weekString == "Friday.")
110
{
111
returnWeekType = Friday;
112
}
113
else if(weekString == "Saturday.")
114
{
115
returnWeekType = Saturday;
116
}
117
else if(weekString == "Sunday.")
118
{
119
returnWeekType = Sunday;
120
}
121
else
122
{
123
returnWeekType = Unknown;
124
}
125
126
return returnWeekType;
127
}
128
129
//设置证言,speakerNum是说话者编号,testimonyWords是证词,testimony是对应的证言结构体(全局)
130
//在函数执行前testimony是未知的(即各个字段没有初始化),而在函数执行后testimony是已知的
131
void SetTestimony(int speakerNum, string testimonyWords, TestimonyStruct &testimony)//运行时间:O(m) [因为调用GetPeopleNum]
132
{
133
//证词的前四个单词,注意证词不包括说话者姓名和姓名后面的“:”符号
134
string word1,word2,word3,word4;
135
word1 = strtok(&(testimonyWords[0])," ");
136
word2 = strtok(NULL," ");
137
word3 = strtok(NULL," ");
138
//如果第3个单词是not的话才读取第4个单词,这是为了避免一行仅有3个单词时读取第4个单词引起的错误
139
if(word3 == "not")
140
word4 = strtok(NULL," ");
141
else
142
word4 = "";
143
144
//设置说话者
145
testimony.SpeakerNum = speakerNum;
146
147
//如果证言是:“I am guilty.”
148
if((word1 == "I") && (word2 == "am") && (word3 == "guilty."))
149
{
150
testimony.Words = IsGuilty;//设置证词类型,下同
151
testimony.Reverse = NotIsGuilty;//设置证词反类型,下同
152
testimony.PeopleNum = speakerNum;//设置证词中出现的人物编号,下同
153
testimony.Week = Unknown;//设置证词中出现的星期,下同
154
}
155
//否则如果证言是:“I am not guilty.”
156
else if((word1 == "I") && (word2 == "am") && (word3 == "not") && (word4 == "guilty."))
157
{
158
testimony.Words = NotIsGuilty;
159
testimony.Reverse = IsGuilty;
160
testimony.PeopleNum = speakerNum;
161
testimony.Week = Unknown;
162
}
163
//否则如果证言是:“XXX 是罪犯”
164
else if((word2 == "is") && (word3 == "guilty."))
165
{
166
int guiltyNum = GetPeopleNum(word1);//获得证词中出现的人物编号,下同
167
testimony.Words = IsGuilty;
168
testimony.Reverse = NotIsGuilty;
169
testimony.PeopleNum = guiltyNum;
170
testimony.Week = Unknown;
171
}
172
//否则如果证言是:“XXX 不是罪犯”
173
else if((word2 == "is") && (word3 == "not") && (word4 == "guilty."))
174
{
175
int guiltyNum = GetPeopleNum(word1);
176
testimony.Words = NotIsGuilty;
177
testimony.Reverse = IsGuilty;
178
testimony.PeopleNum = guiltyNum;
179
testimony.Week = Unknown;
180
}
181
//否则如果证言是:“今天是星期X”
182
else if((word1 == "Today") && (word2 == "is"))
183
{
184
WeekType week = GetWeekType(word3);//获得证词中出现的星期,有可能返回的是Unknown
185
if(week != Unknown)//增加这句话是为了防止诸如“Today is Good Day.”的废话
186
{
187
testimony.Words = TodayIs;
188
testimony.Reverse = TodayNotIs;
189
testimony.PeopleNum = -1;//因为此时我们不应该使用到此值,所以设置为-1,这样如果不小心使用到这个字段的话会因为引用数组下标出界而抛出异常进而提示我们程序有错误
190
testimony.Week = week;
191
}
192
else
193
testimony.Words = NullWords;
194
}
195
else//否则证言没有意义
196
testimony.Words = NullWords;
197
}
198
199
//初始化全局变量,从文件中读取数据
200
void init()//运行时间:O(pm)
201
{
202
ifstream inputFile("logic.in");
203
//读取第一行的m,n,p
204
inputFile>>m>>n>>p;
205
//读取名字,并初始化people
206
for(int i=0; i<m; i++)//每次运行时间:O(m)
207
{
208
string name;
209
inputFile>>name;
210
people[i].Name = name;
211
people[i].Num = i;
212
}
213
//申明一个没用的字符串然后使用函数getline(inputFile,temp,'\n'),这是为了让文件读取指针指向下一行的开头
214
string temp;
215
getline(inputFile,temp,'\n');
216
217
//读取p句证词
218
for(int i=0; i<p; i++)//每次运行时间:O(pm)
219
{
220
//从文件中读取说话者姓名
221
string speakerName;
222
getline(inputFile,speakerName,':');
223
//由姓名得到说话者编号
224
int speakerNum = GetPeopleNum(speakerName);//每次运行时间:O(m)
225
226
//从文件中读取证词
227
string testimonyWords;
228
getline(inputFile,testimonyWords,'\n');
229
//给第i句证词赋值,注意函数执行前testimony[i]的各个字段是未知的
230
SetTestimony(speakerNum,testimonyWords,testimony[i]);//每次运行时间:O(m)
231
}
232
inputFile.close();
233
234
//我们假设编号从0到n-1的这n个人说谎
235
for(int i=0; i<n; i++)//每次运行时间:O(n)
236
StoryPeople[i] = i;
237
for(int i=0; i<m; i++)//每次运行时间:O(m)
238
{
239
isStoryPeople[i] = false;
240
}
241
242
for(int i=0; i<n; i++)//每次运行时间:O(n)
243
{
244
isStoryPeople[StoryPeople[i]] = true;
245
}
246
247
//初始化全局变量guilty
248
for(int i=0; i<n; i++)//每次运行时间:O(n)
249
guilty[i] = 0;
250
}
251
252
//初始化证言验证机器,初始化后验证机认为所有的人都是未知的,并且也不知道今天是星期几
253
void ResetCheckupMachine()//运行时间:O(m)
254
{
255
for(int i=0; i<m; i++)
256
peopleIsGuilty[i] = 0;//别忘了等于0表示未知,等于1表示是罪犯,等于-1表示不是罪犯
257
for(int i=0; i<7; i++)
258
isToday[i] = 0;//类似peopleIsGuilty
259
}
260
261
//验证一句证言,使用前要调用“初始化证言验证机”函数
262
bool Checkup(TestimonyStruct &testimony)//运行时间:O(1)
263
{
264
//申明用于检验的证词类型
265
TestimonyWordsType testimonyWordsType;
266
//获得说话者编号
267
int speakerNum = testimony.SpeakerNum;
268
//如果他说假话
269
if(isStoryPeople[speakerNum])
270
testimonyWordsType = testimony.Reverse;//用于检验的证词为反证词
271
else//否则说真话
272
testimonyWordsType = testimony.Words;
273
274
bool consistent;//consistent=false表示证言不一致(矛盾)
275
//获得证词中所涉及的星期
276
int weekNum = testimony.Week;
277
//分别考虑证词的每种情况,注意证言验证机把testimonyWordsType看为正确的,哪怕它是由说谎者说出来的
278
switch(testimonyWordsType)
279
{
280
//如果证词是“I am guilty.”类型
281
case IsGuilty:
282
//假如证言验证机已经确定此人不是罪犯
283
if(peopleIsGuilty[testimony.PeopleNum] == -1)
284
consistent = false;//证言矛盾
285
else
286
{
287
//否则证言不矛盾
288
consistent = true;
289
//记下曾经有人说过这个人是罪犯
290
peopleIsGuilty[testimony.PeopleNum] = 1;
291
}
292
break;
293
//如果证词是“I am not guilty.”类型
294
case NotIsGuilty:
295
//假如证言验证机已经确定此人就是罪犯
296
if(peopleIsGuilty[testimony.PeopleNum] == 1)
297
consistent = false;//证言矛盾
298
else
299
{
300
//否则证言不矛盾
301
consistent = true;
302
//记下曾经有人说过这个人不是罪犯
303
peopleIsGuilty[testimony.PeopleNum] = -1;
304
}
305
break;
306
//如果证言是“Today is XXX”类型,不失一般性,下面我们假设XXX代表星期三
307
case TodayIs:
308
//如果证言验证机已经确定今天不是星期三
309
if(isToday[weekNum] == -1)
310
consistent = false;//证言矛盾
311
else
312
{
313
//记下今天是星期三
314
isToday[weekNum] = 1;
315
//count用于记算一共确定了几次星期,比如说上一次确定是星期二,而这一次又确定是星期三,那么count就会在isToday[Tuesday]==1时增加1,和在isToday[Wednesday]==1时增加1
316
int count=0;
317
for(int i=0; i<7; i++)
318
if(isToday[i] == 1)
319
count++;
320
//如果仅确定了一次
321
if(count ==1)
322
consistent = true;//证言不矛盾
323
else//否则确定过多次
324
consistent = false;//证言矛盾
325
/*
326
Q:为什么不用一个全局变量来记录今天是星期几呢?
327
A:因为当遇到证言为“Today is not XXX”时证言验证机需要记下今天不是星期几(及设置isToday[XXX]=-1),而这是用一个全局变量做不到的
328
*/
329
}
330
break;
331
//如果证言是:“Today is not XXX”类型,同样,下面我们假设XXX代表星期三
332
case TodayNotIs:
333
//如果证言验证机已经确定今天就是星期三
334
if(isToday[weekNum] == 1)
335
consistent = false;//证言矛盾
336
else
337
{
338
//记下今天不是星期三
339
isToday[weekNum] = -1;
340
//证言不矛盾
341
consistent = true;
342
}
343
break;
344
default:
345
//对于其它废话我们认为它与所有证言都不矛盾
346
consistent = true;
347
break;
348
}
349
//返回此句证言是否矛盾
350
return consistent;
351
}
352
353
//计算下一组说谎话的人,当所有组合均依次出现后函数返回false,关于组合算法请参考我的另一篇文章
354
bool NextGroupStoryPeople()//运行时间:O(m) [注意m>n]
355
{
356
bool hasNext;
357
358
int k = n - 1;
359
while ((k >= 0) && (StoryPeople[k] + (n - k) == m))
360
{
361
k--;
362
}
363
if (k>=0)
364
{
365
if (k==n-1)
366
{
367
StoryPeople[k]++;
368
}
369
else
370
{
371
StoryPeople[k]++;
372
k++;
373
while (k<n)
374
{
375
StoryPeople[k] = StoryPeople[k - 1] + 1;
376
k++;
377
}
378
}
379
hasNext = true;
380
//计算说谎的人,别忘了StoryPeople用来保存组合的状态,便于生成下一个组合,并不用于其它函数,而isStoryPeople在验证证言时会用到
381
for(int i=0; i<m; i++)//每次运行时间:O(m)
382
{
383
isStoryPeople[i] = false;
384
}
385
for(int i=0; i<n; i++)
386
{
387
isStoryPeople[StoryPeople[i]] = true;
388
}
389
}
390
else
391
{
392
hasNext = false;
393
}
394
return hasNext;
395
}
396
397
//运行整个模拟查找过程
398
void run()//运行时间:O(p*C(m,n))+O(m*C(m,n)) [是否等于O((m+p)*C(m,n))???]
399
{
400
401
do//运行时间:C(m,n)*(O(p)+O(m))
402
{
403
//重置证言验证机,以便开始验证接下来的证言
404
ResetCheckupMachine(); //每次运行时间:O(m)
405
406
407
bool consistent = true;//consistent=false表示证言出现矛盾
408
//依次验证p句证言
409
for(int i=0; i<p; i++)//每次运行时间:O(p)
410
{
411
consistent = Checkup(testimony[i]);//每次运行时间:O(1)
412
//如果证言出现矛盾
413
if(!consistent)
414
break;//跳出验证循环
415
}
416
//如果证言不矛盾
417
if(consistent)
418
{
419
//统计罪犯数
420
int guiltyCount=0;
421
for(int i=0; i<m; i++)//每次运行时间:O(m)
422
{
423
//如果第i个人是罪犯,注意peopleIsGuilty在每次重置证言验证机时被初始化
424
if(peopleIsGuilty[i] == 1)
425
{
426
//罪犯数增加1
427
guiltyCount++;
428
}
429
}
430
//如果根据证言能够恰好有一个罪犯(表示在这次由n个人组成说谎者的情况下正好找到了一个罪犯)
431
if(guiltyCount == 1)
432
{
433
//寻找到那个罪犯
434
for(int i=0; i<m; i++)//每次运行时间:O(m)
435
{
436
if(peopleIsGuilty[i] == 1)
437
{
438
guilty[i]++;//此步不懂的话到申明处看看guilty的注释
439
break;
440
}
441
}
442
}
443
else//否则不能恰好找到一个罪犯(根据题目补充点四,我们判断是否恰好有m-1个人不是罪犯)
444
{
445
//统计不是罪犯的人数
446
int notGuiltyCount = 0;
447
for(int i=0; i<m; i++)//每次运行时间:O(m)
448
{
449
if(peopleIsGuilty[i] == -1)
450
{
451
notGuiltyCount++;
452
}
453
}
454
//如果根据证言能够恰好有m-1个人不是罪犯
455
if(notGuiltyCount == m-1)
456
{
457
//找出那个不能推出是不是罪犯的人
458
for(int i=0; i<m; i++)//每次运行时间:O(m)
459
{
460
if(peopleIsGuilty[i] != -1)
461
{
462
//那么我们认为他是罪犯
463
guilty[i]++;
464
break;
465
}
466
}
467
}
468
}
469
}
470
}while(NextGroupStoryPeople());//直到所有说谎组合均考虑过后。运行次数C(m,n),NextGroupStoryPeople运行时间为:O(m)
471
/*
472
我们也可以在找到两个罪犯时退出模拟寻找过程以便提高程序效率,因为题目要求当能找到多于1个罪犯时仅输出“Cannot Determine”
473
*/
474
}
475
476
//输出结果,此函数说明略
477
void show()//运行时间:O(m)
478
{
479
int guiltyCount = 0;
480
string guiltyName;
481
482
for(int i=0; i<m; i++)
483
{
484
if(guilty[i]>0)
485
{
486
guiltyCount++;
487
guiltyName = people[i].Name;
488
}
489
}
490
switch(guiltyCount)
491
{
492
case 0:
493
cout<<"Impossible"<<endl;
494
break;
495
case 1:
496
cout<<guiltyName<<endl;
497
break;
498
default:
499
cout<<"Cannot Determine"<<endl;
500
break;
501
}
502
503
}
504
505
int main(int argc, char *argv[])//运行时间:O(p*C(m,n))+O(m*C(m,n))
506
{
507
init();//初始化全局变量,从输入文件中读取数据
508
run();//运行模拟查找过程
509
show();//输出结果
510
system("PAUSE");
511
return EXIT_SUCCESS;
512
}
513
/*2
=========程序信息========3
对应题目:noip2003提高组_侦探推理4
使用语言:c++5
使用编译器:dev c++6
使用算法:穷举模拟7
算法运行时间:O(p*C(m,n)) + O(m*C(m,n)) [其中C(m,n)是从m个数中取n个数的组合数]8
作者:贵州大学05级 刘永辉 9
昵称:SDJL10
编写时间:2008年8月11
联系QQ:4456190712
E-Mail:44561907@qq.com13
获得更多文章请访问我的博客:www.cnblogs.com/sdjl14
如果发现BUG或有写得不好的地方请发邮件告诉我:)15
=========题目解析========16
在做此题之前,我们需要搞清楚几点,这是我从测试数据中看出而从题目上不容易看出来的:17
题目补充点一:如果有4个人a、b、c、d,当a、b说谎话时我们推出了c是罪犯,然后当a、c说谎话时我们又推出了c是罪犯,此时我们仅推出一个罪犯,而不是两个,因此输出为c,而不是输出Cannot Determine,注意不要重复计数。18
题目补充点二:如果有4个人a、b、c、d,当我们推出a、b、c三人都不是罪犯而d未知时,那么d就是罪犯,这是从某些测试数据中发现的规则,题目没有明说。19
题目补充点三:不会出现重名情况。20
题目补充点四:注意“I am guilty.”、“Today is Monday.”等语句的后面均有一个“.”号。21

22
在确定上面几点后我们来思考此题如何做,首先我想到的是用模拟方法,并且此程序使用的就是模拟方法,也就是说,我们用组合的方式从m个人中取出n个人假设这n个人说谎话,然后分别对这m个人所说的共p句话进行判断,如果p句话均两两不矛盾的话,那么就看看能不能正好找出一个罪犯,或者正好找出m-1个“好人”的话(“好人”指不是罪犯的人,下同),另一个便认为是罪犯。23
同时注意分析,本质上有效的证言仅有4种类型,第一种是“谁是罪犯”,第二种是“谁不是罪犯”,第三种是“今天是星期几”,第四种是“今天不是星期几”,并且如果某个说谎者的证言类型为“谁是罪犯”,那么他的反证言“谁不是罪犯”一定成立。24
无论在这n个人说谎的情况下能不能找出罪犯,都要从m个人中重新找出一组n个人的组合,再次进行判断(即寻找罪犯)。25
直到所有说谎人的组合情况都考虑后再来看哪些人有可能是罪犯,如果超过两个,则输出Cannot Determine,如果正好有一个,则输出此人的名字,如果一个都没有,则输出Impossible。26
*/27

28
#include <cstdlib>29
#include <iostream>30
#include <fstream>31

32
using namespace std;33
const int maxn=20;//最多参与人数34
const int maxTestimony=100;//最多证词数35

36
//证词枚举类型,分别对应“I am guilty.”、“I am not guilty”、“Today is XXX”、“Today not is XXX”及其它废话。37
enum TestimonyWordsType38
{39
IsGuilty,NotIsGuilty,TodayIs,TodayNotIs,NullWords40
};41
//星期枚举类型,分别从“星期一”对应到“星期日”,最后一个是“未知”,注意,枚举事实上是一个从0开始计数的整数,Monday事实上等于0,Tuesday等于1,Unknown等于7,因此后面代码出现诸如“isToday[Monday]=1”的代码时请不要惊讶,这表示今天是星期一42
enum WeekType43
{44
Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday,Unknown45
};46
//证言结构体47
struct TestimonyStruct48
{49
TestimonyWordsType Words;//证言的类型,IsGuilty、NotIsGuilty、TodayIs、TodayNotIs、NullWords五个之一50
TestimonyWordsType Reverse;//证言的反类型,IsGuilty、NotIsGuilty互反,TodayIs、TodayNotIs互反,NullWords的反类型还是NullWords51
52
int PeopleNum;//说话内容里面所涉及到的人的编号,比如说“SDJL is guilty.”那么就是SDJL此人的编号,此字段仅在Words属于IsGuilty、NotIsGuilty之一时有用53
WeekType Week;//说话内容里面所涉及到的星期,比如说“Today is Monday.”那么Week就等于Monday,此字段仅在Words属于TodayIs、TodayNotIs之一时有用54

55
int SpeakerNum;//说话者编号56
};57
//人结构体58
struct PeopleStruct59
{60
string Name;//人名61
int Num;//编号62
};63

64
PeopleStruct people[maxn];//人65
int n;//说谎者人数66
int m;//总人数67
int p;//证言数,注意一个人可能有多句证言,或没有证言68
int StoryPeople[maxn];//StoryPeople[3]=5表示第3个说谎者是people[5](从第0个开始计数),此全局变量仅用于保存组合算法的状态,其它地方不会用到69
bool isStoryPeople[maxn];//isStoryPople[5]=true表示第5个人说的是谎话,此全局变量在组合算法中得到它的值,而在验证机中用来判断某人是否说谎话70
TestimonyStruct testimony[maxTestimony];//证言71
int guilty[maxn];//guilty[3]=2表示两次推出第3个人是罪犯,等于0表示不能推出此人是罪犯,这里我们不能简单的用一个整数来记录找出的罪犯数以及用一个字符串来记录罪犯的名字,这是因为要避免前面所提到的“题目补充点一”情况。72

73
//这两个全局变量用于验证机 74
int peopleIsGuilty[maxn];//peopleIsGuilty[7]=1表示第7个人是罪犯,=-1表示不是罪犯,=0表示未知 75
int isToday[7];//isToday[3]=1 表示今天是星期四, isToday[6]=-1 表示今天不是星期日,别忘了是从0开始计数,等于0时表示未知,isToday数组中最多只能有一个为176

77
//根据人名得到此人的编号78
int GetPeopleNum(string name)//运行时间:O(m) 79
{80
for(int i=0; i<m; i++) 81
{82
if(people[i].Name == name)83
return i; 84
}85
//这里应该抛出异常,表示给出的人名错误 86
}87

88
//根据星期名称字符串得到星期枚举类型89
WeekType GetWeekType(string weekString)//运行时间:O(1)90
{91
//字符串居然不能用于switch语句?这是我没有预料到的92
WeekType returnWeekType;93
if(weekString == "Monday.")94
{95
returnWeekType = Monday;96
}97
else if(weekString == "Tuesday.") 98
{99
returnWeekType = Tuesday;100
}101
else if(weekString == "Wednesday.")102
{103
returnWeekType = Wednesday; 104
}105
else if(weekString == "Thursday.")106
{107
returnWeekType = Thursday; 108
}109
else if(weekString == "Friday.")110
{111
returnWeekType = Friday; 112
}113
else if(weekString == "Saturday.")114
{115
returnWeekType = Saturday; 116
}117
else if(weekString == "Sunday.")118
{119
returnWeekType = Sunday; 120
}121
else 122
{123
returnWeekType = Unknown;124
}125
126
return returnWeekType;127
}128

129
//设置证言,speakerNum是说话者编号,testimonyWords是证词,testimony是对应的证言结构体(全局)130
//在函数执行前testimony是未知的(即各个字段没有初始化),而在函数执行后testimony是已知的131
void SetTestimony(int speakerNum, string testimonyWords, TestimonyStruct &testimony)//运行时间:O(m) [因为调用GetPeopleNum]132
{133
//证词的前四个单词,注意证词不包括说话者姓名和姓名后面的“:”符号134
string word1,word2,word3,word4;135
word1 = strtok(&(testimonyWords[0])," ");136
word2 = strtok(NULL," ");137
word3 = strtok(NULL," ");138
//如果第3个单词是not的话才读取第4个单词,这是为了避免一行仅有3个单词时读取第4个单词引起的错误139
if(word3 == "not")140
word4 = strtok(NULL," ");141
else142
word4 = "";143
144
//设置说话者 145
testimony.SpeakerNum = speakerNum;146
147
//如果证言是:“I am guilty.” 148
if((word1 == "I") && (word2 == "am") && (word3 == "guilty."))149
{150
testimony.Words = IsGuilty;//设置证词类型,下同151
testimony.Reverse = NotIsGuilty;//设置证词反类型,下同152
testimony.PeopleNum = speakerNum;//设置证词中出现的人物编号,下同153
testimony.Week = Unknown;//设置证词中出现的星期,下同154
}155
//否则如果证言是:“I am not guilty.” 156
else if((word1 == "I") && (word2 == "am") && (word3 == "not") && (word4 == "guilty."))157
{158
testimony.Words = NotIsGuilty;159
testimony.Reverse = IsGuilty;160
testimony.PeopleNum = speakerNum;161
testimony.Week = Unknown; 162
}163
//否则如果证言是:“XXX 是罪犯”164
else if((word2 == "is") && (word3 == "guilty.")) 165
{166
int guiltyNum = GetPeopleNum(word1);//获得证词中出现的人物编号,下同167
testimony.Words = IsGuilty;168
testimony.Reverse = NotIsGuilty;169
testimony.PeopleNum = guiltyNum;170
testimony.Week = Unknown; 171
}172
//否则如果证言是:“XXX 不是罪犯”173
else if((word2 == "is") && (word3 == "not") && (word4 == "guilty."))174
{175
int guiltyNum = GetPeopleNum(word1);176
testimony.Words = NotIsGuilty;177
testimony.Reverse = IsGuilty;178
testimony.PeopleNum = guiltyNum;179
testimony.Week = Unknown; 180
}181
//否则如果证言是:“今天是星期X”182
else if((word1 == "Today") && (word2 == "is")) 183
{184
WeekType week = GetWeekType(word3);//获得证词中出现的星期,有可能返回的是Unknown185
if(week != Unknown)//增加这句话是为了防止诸如“Today is Good Day.”的废话 186
{187
testimony.Words = TodayIs;188
testimony.Reverse = TodayNotIs;189
testimony.PeopleNum = -1;//因为此时我们不应该使用到此值,所以设置为-1,这样如果不小心使用到这个字段的话会因为引用数组下标出界而抛出异常进而提示我们程序有错误190
testimony.Week = week;191
}192
else193
testimony.Words = NullWords;194
}195
else//否则证言没有意义196
testimony.Words = NullWords;197
} 198

199
//初始化全局变量,从文件中读取数据200
void init()//运行时间:O(pm)201
{202
ifstream inputFile("logic.in");203
//读取第一行的m,n,p 204
inputFile>>m>>n>>p;205
//读取名字,并初始化people 206
for(int i=0; i<m; i++)//每次运行时间:O(m)207
{208
string name;209
inputFile>>name;210
people[i].Name = name; 211
people[i].Num = i; 212
}213
//申明一个没用的字符串然后使用函数getline(inputFile,temp,'\n'),这是为了让文件读取指针指向下一行的开头214
string temp;215
getline(inputFile,temp,'\n');216
217
//读取p句证词 218
for(int i=0; i<p; i++)//每次运行时间:O(pm)219
{220
//从文件中读取说话者姓名221
string speakerName;222
getline(inputFile,speakerName,':');223
//由姓名得到说话者编号224
int speakerNum = GetPeopleNum(speakerName);//每次运行时间:O(m)225
226
//从文件中读取证词227
string testimonyWords;228
getline(inputFile,testimonyWords,'\n');229
//给第i句证词赋值,注意函数执行前testimony[i]的各个字段是未知的230
SetTestimony(speakerNum,testimonyWords,testimony[i]);//每次运行时间:O(m)231
} 232
inputFile.close();233
234
//我们假设编号从0到n-1的这n个人说谎235
for(int i=0; i<n; i++)//每次运行时间:O(n)236
StoryPeople[i] = i;237
for(int i=0; i<m; i++)//每次运行时间:O(m)238
{239
isStoryPeople[i] = false; 240
}241
242
for(int i=0; i<n; i++)//每次运行时间:O(n)243
{244
isStoryPeople[StoryPeople[i]] = true;245
}246

247
//初始化全局变量guilty248
for(int i=0; i<n; i++)//每次运行时间:O(n)249
guilty[i] = 0;250
}251

252
//初始化证言验证机器,初始化后验证机认为所有的人都是未知的,并且也不知道今天是星期几253
void ResetCheckupMachine()//运行时间:O(m)254
{255
for(int i=0; i<m; i++)256
peopleIsGuilty[i] = 0;//别忘了等于0表示未知,等于1表示是罪犯,等于-1表示不是罪犯257
for(int i=0; i<7; i++)258
isToday[i] = 0;//类似peopleIsGuilty259
}260

261
//验证一句证言,使用前要调用“初始化证言验证机”函数262
bool Checkup(TestimonyStruct &testimony)//运行时间:O(1)263
{264
//申明用于检验的证词类型265
TestimonyWordsType testimonyWordsType;266
//获得说话者编号267
int speakerNum = testimony.SpeakerNum;268
//如果他说假话269
if(isStoryPeople[speakerNum])270
testimonyWordsType = testimony.Reverse;//用于检验的证词为反证词271
else//否则说真话 272
testimonyWordsType = testimony.Words;273

274
bool consistent;//consistent=false表示证言不一致(矛盾) 275
//获得证词中所涉及的星期276
int weekNum = testimony.Week;277
//分别考虑证词的每种情况,注意证言验证机把testimonyWordsType看为正确的,哪怕它是由说谎者说出来的278
switch(testimonyWordsType)279
{280
//如果证词是“I am guilty.”类型281
case IsGuilty:282
//假如证言验证机已经确定此人不是罪犯283
if(peopleIsGuilty[testimony.PeopleNum] == -1)284
consistent = false;//证言矛盾285
else286
{287
//否则证言不矛盾288
consistent = true;289
//记下曾经有人说过这个人是罪犯290
peopleIsGuilty[testimony.PeopleNum] = 1;291
}292
break;293
//如果证词是“I am not guilty.”类型294
case NotIsGuilty:295
//假如证言验证机已经确定此人就是罪犯296
if(peopleIsGuilty[testimony.PeopleNum] == 1)297
consistent = false;//证言矛盾298
else299
{300
//否则证言不矛盾301
consistent = true;302
//记下曾经有人说过这个人不是罪犯303
peopleIsGuilty[testimony.PeopleNum] = -1; 304
} 305
break;306
//如果证言是“Today is XXX”类型,不失一般性,下面我们假设XXX代表星期三307
case TodayIs:308
//如果证言验证机已经确定今天不是星期三309
if(isToday[weekNum] == -1)310
consistent = false;//证言矛盾311
else312
{313
//记下今天是星期三314
isToday[weekNum] = 1;315
//count用于记算一共确定了几次星期,比如说上一次确定是星期二,而这一次又确定是星期三,那么count就会在isToday[Tuesday]==1时增加1,和在isToday[Wednesday]==1时增加1316
int count=0;317
for(int i=0; i<7; i++)318
if(isToday[i] == 1)319
count++;320
//如果仅确定了一次321
if(count ==1)322
consistent = true;//证言不矛盾323
else//否则确定过多次324
consistent = false;//证言矛盾325
/*326
Q:为什么不用一个全局变量来记录今天是星期几呢?327
A:因为当遇到证言为“Today is not XXX”时证言验证机需要记下今天不是星期几(及设置isToday[XXX]=-1),而这是用一个全局变量做不到的328
*/329
} 330
break;331
//如果证言是:“Today is not XXX”类型,同样,下面我们假设XXX代表星期三332
case TodayNotIs:333
//如果证言验证机已经确定今天就是星期三334
if(isToday[weekNum] == 1)335
consistent = false;//证言矛盾336
else337
{338
//记下今天不是星期三339
isToday[weekNum] = -1;340
//证言不矛盾341
consistent = true;342
} 343
break;344
default:345
//对于其它废话我们认为它与所有证言都不矛盾346
consistent = true;347
break; 348
}349
//返回此句证言是否矛盾350
return consistent;351
}352

353
//计算下一组说谎话的人,当所有组合均依次出现后函数返回false,关于组合算法请参考我的另一篇文章 354
bool NextGroupStoryPeople()//运行时间:O(m) [注意m>n]355
{356
bool hasNext;357
358
int k = n - 1;359
while ((k >= 0) && (StoryPeople[k] + (n - k) == m))360
{361
k--;362
}363
if (k>=0)364
{365
if (k==n-1)366
{367
StoryPeople[k]++;368
}369
else370
{371
StoryPeople[k]++;372
k++;373
while (k<n)374
{375
StoryPeople[k] = StoryPeople[k - 1] + 1;376
k++;377
}378
}379
hasNext = true;380
//计算说谎的人,别忘了StoryPeople用来保存组合的状态,便于生成下一个组合,并不用于其它函数,而isStoryPeople在验证证言时会用到381
for(int i=0; i<m; i++)//每次运行时间:O(m)382
{383
isStoryPeople[i] = false; 384
}385
for(int i=0; i<n; i++)386
{387
isStoryPeople[StoryPeople[i]] = true;388
}389
}390
else391
{392
hasNext = false;393
}394
return hasNext; 395
}396

397
//运行整个模拟查找过程398
void run()//运行时间:O(p*C(m,n))+O(m*C(m,n)) [是否等于O((m+p)*C(m,n))???]399
{400
401
do//运行时间:C(m,n)*(O(p)+O(m))402
{ 403
//重置证言验证机,以便开始验证接下来的证言404
ResetCheckupMachine(); //每次运行时间:O(m)405
406
407
bool consistent = true;//consistent=false表示证言出现矛盾 408
//依次验证p句证言409
for(int i=0; i<p; i++)//每次运行时间:O(p)410
{411
consistent = Checkup(testimony[i]);//每次运行时间:O(1)412
//如果证言出现矛盾413
if(!consistent)414
break;//跳出验证循环415
} 416
//如果证言不矛盾 417
if(consistent)418
{419
//统计罪犯数420
int guiltyCount=0;421
for(int i=0; i<m; i++)//每次运行时间:O(m)422
{423
//如果第i个人是罪犯,注意peopleIsGuilty在每次重置证言验证机时被初始化424
if(peopleIsGuilty[i] == 1)425
{426
//罪犯数增加1427
guiltyCount++; 428
}429
} 430
//如果根据证言能够恰好有一个罪犯(表示在这次由n个人组成说谎者的情况下正好找到了一个罪犯)431
if(guiltyCount == 1)432
{433
//寻找到那个罪犯434
for(int i=0; i<m; i++)//每次运行时间:O(m)435
{436
if(peopleIsGuilty[i] == 1)437
{438
guilty[i]++;//此步不懂的话到申明处看看guilty的注释439
break;440
}441
} 442
} 443
else//否则不能恰好找到一个罪犯(根据题目补充点四,我们判断是否恰好有m-1个人不是罪犯)444
{445
//统计不是罪犯的人数446
int notGuiltyCount = 0;447
for(int i=0; i<m; i++)//每次运行时间:O(m)448
{449
if(peopleIsGuilty[i] == -1)450
{451
notGuiltyCount++; 452
}453
} 454
//如果根据证言能够恰好有m-1个人不是罪犯455
if(notGuiltyCount == m-1)456
{457
//找出那个不能推出是不是罪犯的人458
for(int i=0; i<m; i++)//每次运行时间:O(m)459
{ 460
if(peopleIsGuilty[i] != -1)461
{462
//那么我们认为他是罪犯463
guilty[i]++;464
break;465
}466
} 467
} 468
}469
}470
}while(NextGroupStoryPeople());//直到所有说谎组合均考虑过后。运行次数C(m,n),NextGroupStoryPeople运行时间为:O(m) 471
/*472
我们也可以在找到两个罪犯时退出模拟寻找过程以便提高程序效率,因为题目要求当能找到多于1个罪犯时仅输出“Cannot Determine”473
*/474
}475

476
//输出结果,此函数说明略477
void show()//运行时间:O(m)478
{479
int guiltyCount = 0;480
string guiltyName;481

482
for(int i=0; i<m; i++)483
{484
if(guilty[i]>0)485
{486
guiltyCount++;487
guiltyName = people[i].Name;488
}489
}490
switch(guiltyCount)491
{492
case 0:493
cout<<"Impossible"<<endl;494
break; 495
case 1:496
cout<<guiltyName<<endl;497
break;498
default:499
cout<<"Cannot Determine"<<endl;500
break;501
}502
503
}504

505
int main(int argc, char *argv[])//运行时间:O(p*C(m,n))+O(m*C(m,n))506
{507
init();//初始化全局变量,从输入文件中读取数据508
run();//运行模拟查找过程509
show();//输出结果510
system("PAUSE");511
return EXIT_SUCCESS;512
}513



浙公网安备 33010602011771号