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

爱因斯坦曾在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

时间: 2024-10-10 02:52:17

用程序解密爱因斯坦经典难题(C++)的相关文章

微信小程序解密微信运动数据

微信小程序API-微信运动 https://mp.weixin.qq.com/debug/wxadoc/dev/api/we-run.html#wxgetwerundataobject 思路:wx.login获取的code请求获取的session_key,wx.getWeRunData获取的iv,encryptData,将它们一起发送到后台解密就行了. 安全顾虑,因为只是示例所以直接传递session_key了,为了安全最好按照下图的方式加密后存储到Redis中再传递key. 小程序端代码 ge

C# .net 填充无效,无法被移除 微信小程序解密失败的解决办法

微信小程序获取用户信息诸如unionId的时候需要解密,如果遇到偶然的解密失败(填充无效,无法被移除),原因很有可能是session_key错误, 也是就你用作解密的session_key并不是微信用作加密的那个了,但是并不代表你的session_key已经失效. C#解密代码(亲测有效,可以直接复制使用) /// <summary> /// Aes解密 /// </summary> /// <param name="str">需要解密的字符串<

(转)《疯狂的程序员》经典语录

该博客转自新浪博客,作者:疯狂之桥 网址: http://blog.sina.cn/dpool/blog/s/blog_a46817ff010157cf.html?vt=4 今天看到绝影的CSDN博客由于种种原因被关闭了,心中有种说不出的感觉....感谢在我还是大二的时候遇到了<疯狂的程序员> (已下是转自内容) 朋友介绍说看看<疯狂的程序员>这本书吧!说实话,自从进入浮躁的青春期后,从来没有用心的看过一本书,包括小说. 但这本书,昨晚看完之后,今天我又看了一遍.看这些故事的时候,

分享:程序员的经典书籍,满满的干货,附下载链接!

一.Web 前端开发 <HTTP 权威指南> 该书分为五部分,分别讲述 Web 基础知识,HTTP 结构,识别 / 验证与安全,实体 / 编码和国际化,及内容发布与分发.介绍 HTTP 协议,详细描述 Web 网络资源 URL/URI,并介绍 HTTP 是如何传输报文:介绍了 Web 服务器结构,深入代理服务器研究,缓存及应用服务器的作用,还介绍 Web 服务的各种客户端,包括浏览器爬虫等:研究 HTTP 身份识别与验证,讨论 SSL 等安全性问题:详细讲解 HTTP 内容结构,规范编码,多语

Java中基本数据类型的存储方式和相关内存的处理方式(java程序员必读经典)

1.java是如何管理内存的 java的内存管理就是对象的分配和释放问题.(其中包括两部分) 分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(Heap)中分配空间. 释放:对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作.但同时,它也加重了JVM的工作.因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请.引用.被引用.赋值等,GC都需要进行监控. 2.什么叫java的内存泄露 在

OSChina 周六乱弹 —— 程序员专用经典语录

1 IT人表示屁股上还得纹一个</body>, 要不中间来个hello world! 2 真正的程序员喜欢兼卖爆米花,他们利用CPU散发出的热量做爆米花,可以根据米花爆裂的速度听出正在运行什么程序. 3 十年生死两茫茫,写程序,到天亮. 千行代码,Bug何处藏. 纵使上线又怎样,朝令改,夕断肠. 领导每天新想法,天天改,日日忙. 相顾无言,惟有泪千行. 每晚灯火阑珊处,夜难寐,又加班. 4 老婆给当程序员的老公打电话:"下班顺路买三个包子带回来,如果看到卖西瓜的,买一个."

python练习程序(c100经典例15)

题目: 利用条件运算符的嵌套来完成此题:学习成绩〉=90分的同学用A表示,60-89分之间的用B表示,60分以下的用C表示. def foo(n): if n>=90: print 'A' , elif n>=60: print 'B' , else: print 'C' , for i in range(1,101): foo(i)

python练习程序(c100经典例16)

题目: 输入两个正整数m和n,求其最大公约数和最小公倍数. def foo(a,b): if a<b: (a,b)=(b,a) aa=a; bb=b; while b!=0: tmp=a%b; a=b; b=tmp; print '最大公约数:'+str(a) print '最小公倍数:'+str(aa*bb/a) foo(4,3)

python练习程序(c100经典例14)

题目: 将一个正整数分解质因数.例如:输入90,打印出90=2*3*3*5. def foo(n): while 1: for i in range(2,n+1): if n%i==0: print i, n=n/i; break; if n==1: break; for i in range(1,100): foo(i) print