吴昊品游戏核心算法(新年特别篇)—— 称硬币游戏AI(朴素枚举)(POJ 2692)
吴昊继续,首先还是来一段人生感悟吧——在我很小的时候,大约是初中的时候,我知道要让自己的人生混得好就应该思考许多问题,所以我喜欢思考;十几年过去 了,我领悟到要让自己的人生混得好就应该不思考许多问题,但是,我仍然喜爱思考,无论是对是错,总之,我思考过,并有自己的感悟,我觉得这乃是一件很好的 事情,所以,我接着思考,并决定在寒假之前完成自己的《吴昊品历史人物》第一季(Round 1--Round 10)
在我年轻时,我所做的事,十中有九都是失败的,为了不甘于失败,我便十倍努力于工作。——萧伯纳
如图所示,此为一个普通的天平,但是没有标度,也就是说,我们根据其只能判断出是轻了,重了,还是一样的。我们目前有N个硬币(这里应该好更改的,这里以经 典的问题为主,就是12个硬币有一个是假的却又不知道轻重,这样的话,称三次是可以称出来的,只要方法得当即可,当然,这里已经假设作者已经设计出了正确 的方案,如何设计呢?见下文)
站在逻辑推理角度来思考
有12个硬币和一个天平,硬币中有一个是假的,它与真币质量不同。请你用天平称这些硬币,在三次之内得到两个结果:(1)哪个硬币是假币?(2)假币比真币轻还是重?
答案:先取出八个硬币,在天平两边各放四个。(第一次)
1. 一样重。
说明假币在剩下四个里面。给嫌疑币编号ABCD,未编号硬币为O。接着这样称:ABC_OOO(第二次)
(1)一样重。
说明最后一个是假币。然后这样称:D_O(第三次)。这样就知道假币是轻了还是重了。
(2)左面轻(重)
说明ABC里面有一个假币,且比真币轻(重)。然后这样称:A_B(第三次)。
①一样重。说明C轻(重),是假币。
②不一样重。说明轻(重)的是假的。
2. 不一样重。(不妨设左面轻)
说明假币在这八个硬币里面,编号为ABCD和EFGH。接着这样称:AOOO_EBCD(第二次)
(1)一样重。
说明FGH里有一个重了,是假币。然后这样称:F_G(第三次)。
①一样重。说明H是假币,重。
②不一样重。说明重的是假币。
(2)左面轻。
说明A轻或E重。然后这样称:A_O(第三次)就知道A是不是假的。
(3)右面轻。
说明BCD里面有一个轻了,是假币。然后这样称:B_C(第三次)。
①一样重。说明D是假币,轻。
②不一样重。说明轻的是假币。
站在算法的角度来思考:
这样做,倒是给计算机节省了不少负担的说,但是,这里发挥了人类的智慧,却没有利用计算机的特长(高效率的计算能力),那么,对于计算机来说还是不划算 的。况且,当硬币的数目更改之后,我们又要重新找到一些方法,这样来说的话,十分麻烦。于是,站在计算机的角度,我们利用朴素枚举来解决问题,这样做可以 增强普遍性,硬币的数量N一改,也很容易更换代码。由于时间复杂度为O(N^2log3(n)),所以暴力一下也是可以接受的。
我最开始的时候误以为时间复杂度是O(N)的,后来想通了,并在第二季中给出了解释,这里搬回来。
作为首映式,首先,纠正一下以前的错误吧,在吴昊系列的 “称硬币游戏AI”中,我说的时间复杂度是O(n),实际上有问题,首先,确实将所有的硬币都遍历了一遍,但是,每遍历一个硬币的时候,strchr函数 将遍历一个天平上的所有硬币,这样是O(n/2),还没有完,因为称的次数与硬币的数目是正相关的,那么,至少是O(n^2)以上,一篇论文中用信息熵来 解决这个问题!,得到如果N个硬币有一个(可能没有)为假币,至少要称量K=log(2N+1)/log3次,那么,可以得到时间复杂度的上限为 O(n^2log3n)。
Solve:
利用isHeavy(char x)和isLight(char x)对每一种可能的硬币进行枚举(或者可以称为暴力吧),得到合适的哪一个。
2 //涉及到字符串的一些处理
3 #include<string.h>
4
5 /*以字符串数组存储称量的结果。每次称量的时候,天平左右最多有6枚
6 硬币。因此,字符串的长度为7,最后一位存储字符串的结束符'\0',
7 便于程序代码中使用字符串的操作函数
8 */
9 char left[3][7],right[3][7],result[3][7];
10
11 //对每一个硬币进行试探
12 //x比真币重的猜测是否成立?如果成立则输出
13 bool isHeavy(char x)
14 {
15 int i;
16 for(i=0;i<3;i++)
17 {
18 switch(result[i][0])
19 {
20 case 'u': if(strchr(left[i],x)==NULL) return false;
21 break;
22 case 'e': if(strchr(right[i],x)!=NULL||strchr(left[i],x)!=NULL) return false;
23 break;
24 case 'd': if(strchr(right[i],x)==NULL) return false;
25 break;
26 }
27 }
28 return true;
29 }
30
31 //x比真币轻的猜测是否成立?如果成立则输出
32 bool isLight(char x)
33 {
34 int i;
35 //判断选择的那一个硬币与三次结果是否矛盾
36 for(i=0;i<3;i++)
37 {
38 //strchr函数为逐个字符串比对
39 switch(result[i][0])
40 {
41 case 'u': if(strchr(right[i],x)==NULL) return false;
42 break;
43 case 'e': if(strchr(right[i],x)!=NULL||strchr(left[i],x)!=NULL) return false;
44 break;
45 case 'd': if(strchr(left[i],x)==NULL) return false;
46 break;
47 }
48 }
49 return true;
50 }
51
52 int main()
53 {
54 int n;
55 char c;
56 //读入案例的数目
57 scanf("%d",&n);
58 while(n--)
59 {
60 for(int i=0;i<n;i++)
61 {
62 //对于字符串的读入不需要&取址
63 scanf("%s%s%s",left[i],right[i],result[i]);
64 }
65 for(c='A';c<='L';c++)
66 {
67 if(isLight(c))
68 {
69 printf("%c is the counterfeit coin and it is light.\n",c);
70 break;
71 }
72 if(isHeavy(c))
73 {
74 printf("%c is the counterfeit coin and it is heavy.\n",c);
75 break;
76 }
77 }
78 //如果还需要找到一个结论的话,需要标注flag,这里略去
79 }
80 return 0;
81 }