用程序解密爱因斯坦经典难题(C++)

来源:互联网 发布:冲压模拟软件 编辑:程序博客网 时间:2024/06/10 22:19
爱因斯坦曾在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

0 0
原创粉丝点击