爱因斯坦曾在20世纪初提过一个经典问题,据说世界上有98%的人回答不出来
问题:在一条街上,有5座房子,喷了5中颜色。每个房子住着不同国籍的人。每个人喝不同的饮料,抽不同品牌的香烟,养不同的宠物。
问题是:谁养鱼?
提示:1.英国人住红色房子
2.瑞典人养狗
3.丹麦人喝茶
4.绿色房子在白色房子左边
5.绿房子主人喝咖啡
6.抽PallMall香烟的人养鸟
7.黄色房子的主人抽Dunhill香烟
8.住在中间房子的人喝牛奶
9.挪威人住第一间房
10.抽Bleeds香烟的人住在养猫的人隔壁
11.养马的人住抽Dunhill香烟的人隔壁
12.抽BlueMaster的人喝啤酒
13.德国人抽Prince香烟
14.挪威人住蓝色房子隔壁
15.抽Bleeds香烟的人有一个喝水的邻居
这个版本的程序我将不会使用任何推理,仅仅用程序描述这15个条件,然后穷举所有组合得到满足这15个条件的唯一解答。
首先构造“人”这个类型,根据题目可以知道每个人拥有颜色,香烟,饮料,宠物,位置,国籍这6个属性。虽然在现实中,人抽香烟,喝饮料,养宠物,拥有国籍,只有这4个属性,颜色和位置是房子的属性,但是人住在哪间房子,就决定了这个人的颜色和位置,所以简便起见,这6种属性全部归类到一个抽象的“人”中。新建一个Person.h的文件:
class Person { public: Person(Color,Country ,Drink ,Cigarette ,Pet ,int); ~Person(void); Color color; Country country; Drink drink; Cigarette cigarette; Pet pet; int index; };
根据这6种属性,可以创造出5^6个不同的“人”。接着,观察这15个条件,条件1,2,3,5,6,7,8,9,12,13,14,全部是以单个人作为约束条件,而剩下的4个都是以2个人之间的关系作为约束条件。首先可以把不满足条件的人过滤掉,再通过组合判断剩下的条件是否满足。比如1.英国人住红色房子,那么国籍是英国但是颜色不是红色的人就是不满足条件的,反过来,颜色是红色但是国籍不是英国的人也不满足条件,用程序的描述就是:
bool test1(const Person &p) { if((p.country==England&&p.color!=Red)||(p.country!=England&&p.color==Red)) return false; return true; }
同理剩下的以人为约束的条件描述为:
bool test2(const Person &p) { if((p.country==Sweden&&p.pet!=Dog)||(p.country!=Sweden&&p.pet==Dog)) return false; return true; } bool test3(const Person &p) { if((p.country==Denmark&&p.drink!=Tea)||(p.country!=Denmark&&p.drink==Tea)) return false; return true; } bool test5(const Person &p) { if((p.color==Green&&p.drink!=Coffee)||(p.color!=Green&&p.drink==Coffee)) return false; return true; } bool test6(const Person &p) { if((p.cigarette==PallMall&&p.pet!=Bird)||(p.cigarette!=PallMall&&p.pet==Bird)) return false; return true; } bool test7(const Person &p) { if((p.color==Yellow&&p.cigarette!=Dunhill)||(p.color!=Yellow&&p.cigarette==Dunhill)) return false; return true; } bool test8(const Person &p) { if((p.index==3&&p.drink!=Milk)||(p.index!=3&&p.drink==Milk)) return false; return true; } bool test9(const Person &p) { if((p.country==Norway&&p.index!=1)||(p.country!=Norway&&p.index==1)) return false; return true; } bool test12(const Person &p) { if((p.cigarette==BlueMaster&&p.drink!=Bear)||(p.cigarette!=BlueMaster&&p.drink==Bear)) return false; return true; } bool test13(const Person &p) { if((p.country==Germany&&p.cigarette!=Prince)||(p.country!=Germany&&p.cigarette==Prince)) return false; return true; } //条件14可直接表示为蓝色房子是第二间房 bool test14(const Person &p) { if((p.color==Blue&&p.index!=2)||(p.color!=Blue&&p.index==2)) return false; return true; }
筛选出了有效的“人”,接着把这些“人”装进不同的"房子"里
vector<Person> v1; vector<Person> v2; vector<Person> v3; vector<Person> v4; vector<Person> v5; for(int color=0;color<5;++color) for(int country=0;country<5;++country) for(int drink=0;drink<5;++drink) for(int cigarette=0;cigarette<5;++cigarette) for(int pet=0;pet<5;++pet) for(int index=1;index<=5;++index) { Person p((Color)color,(Country)country,(Drink)drink,(Cigarette)cigarette,(Pet)pet,index); if(test1(p)&&test2(p)&&test3(p)&&test5(p)&&test6(p)&&test7(p)&&test8(p)&&test9(p)&&test12(p)&&test13(p)&&test14(p)) { switch(p.index) { case 1: v1.push_back(p); break; case 2: v2.push_back(p); break; case 3: v3.push_back(p); break; case 4: v4.push_back(p); break; case 5: v5.push_back(p); break; } } }
5个集合分别表示编号为1-5的5个房子,通过循环构建“人”,把符合条件的人扔到不同的房子里去。这样一来,得到了5个房子,每个房子里都有一些人,这些人都是已经满足了前面所有条件的,接着每个房子里分别派出一个人,判断这5个人是否能满足剩下的条件,如果满足,那这5个人就是答案,不满足,那就换人,直到所有的人都组合过就结束。
size_t s1=v1.size(); size_t s2=v2.size(); size_t s3=v3.size(); size_t s4=v4.size(); size_t s5=v5.size(); int count=0; string countryName[5]={"英国人","瑞典人","丹麦人","挪威人","德国人"}; string colorName[5]={"蓝色","绿色","白色","红色","黄色"}; string drinkName[5]={"茶","牛奶","咖啡","啤酒","水"}; string cigaretteName[5]={"PallMall","Dunhill","Bleeds","BlueMaster","Prince"}; string petName[5]={"鱼","狗","鸟","猫","马"}; for(int i1=0;i1<s1;++i1) for(int i2=0;i2<s2;++i2) for(int i3=0;i3<s3;++i3) for(int i4=0;i4<s4;++i4) for(int i5=0;i5<s5;++i5) { Person pp[5]={v1[i1],v2[i2],v3[i3],v4[i4],v5[i5]}; if(testDistinct(pp)&&test4(pp)&&test10(pp)&&test11(pp)&&test15(pp)) { cout<<"答案"<<++count<<":"<<endl; for(int i=0;i<5;++i) { cout<<"第"<<i+1<<"间房是"<<colorName[pp[i].color]<<",住的是"<<countryName[pp[i].country]<<",喝"<<drinkName[pp[i].drink]<<",抽"<<cigaretteName[pp[i].cigarette]<<",养"<<petName[pp[i].pet]<<endl; } } }
这里除了剩下的4个条件还有一个隐藏条件,那就是人的属性是不能重复的,例如这5个人当中不能有2个英国人,也不能有2个人或者3个人同时养鱼。
去重的验证,这里用了一点小技巧:
bool testDistinct(Person *pp) { int state[25]={0}; for(int i=0;i<5;++i) { for(int j=0;j<5;++j) { state[j]+=pp[i].color==(Color)j; state[j+5]+=pp[i].drink==(Drink)j; state[j+10]+=pp[i].cigarette==(Cigarette)j; state[j+15]+=pp[i].pet==(Pet)j; state[j+20]+=pp[i].country==(Country)j; } } for(int i=0;i<25;i++) { if(state[i]!=1) return false; } return true; }
人已经被分到不同房子里了,所以这5个人的index肯定是不会重复了,只需判断剩下的5个属性是否重复。每种属性都是5个状态,5种属性就是25个状态,然后用一个长度为25的一维数组来表示,比如state[0]表示蓝色属性的数量,state[1]表示绿色属性的数量,state[5]表示喝茶人的数量,然后5个人循环判断是否存在这些状态,如果有那么state数组计数就加1。如果组合是不存在重复的,也就表示state数组的每一项都正好是1,如果其中某一项不等于1,就表示一定存在重复。
剩下的4个条件判断:
bool test4(Person *pp) { Person *p1,*p2; for(int i=0;i<5;++i) { if(pp[i].color==Green) p1=&pp[i]; if(pp[i].color==White) p2=&pp[i]; } return p1->index==p2->index-1; } bool test10(Person *pp) { Person *p1,*p2; for(int i=0;i<5;++i) { if(pp[i].cigarette==Bleeds) p1=&pp[i]; if(pp[i].pet==Cat) p2=&pp[i]; } return p1->index==p2->index-1||p1->index==p2->index+1; } bool test11(Person *pp) { Person *p1,*p2; for(int i=0;i<5;++i) { if(pp[i].pet==Horse) p1=&pp[i]; if(pp[i].cigarette==Dunhill) p2=&pp[i]; } return p1->index==p2->index-1||p1->index==p2->index+1; } bool test15(Person *pp) { Person *p1,*p2; for(int i=0;i<5;++i) { if(pp[i].drink==Water) p1=&pp[i]; if(pp[i].cigarette==Bleeds) p2=&pp[i]; } return p1->index==p2->index-1||p1->index==p2->index+1; }
原理就是从5个人中找到条件中的2个人,然后判断他们的index属性,所谓邻居,隔壁都是表示他们的index是相连的。
到这里代码就已经写完了,最后的计算结果是:
最后总结:为什么有的问题人无法解决却可以用计算机解决,虽然这个题目用画表格的方法推理出答案也不太难,但是相比较而言,计算机程序的思路更简单粗暴,而人不可能用这种方式思考问题。假如你要计算987*512,而你并不会乘法口诀,你怎么得到结果?这时给了你一张无比巨大的草稿纸,而且在上面的书写速度会非常快,幸好你的加法还不错,于是你不断演算987+987=1974,1974+987=2961...至到加上512个987为止,最终你得到了正确答案,由于书写速度非常快,甚至快过你用乘法口诀得出答案,这就是计算机解决人无法解决问题的原理,计算机就好比这样的草稿纸。至于如何把问题描述给计算机计算(把乘法问题转成加法问题),以及让计算机运行得更快(你可以累加512次,你也可以仅仅累加9次),这就涉及到计算机科学的一门重要课程--数据结构与算法。
本文章原创地址:http://blog.csdn.net/maidou0921/article/details/51910075