C语言每日一练
2022年1月14日
题目描述
已知:诚实族和说谎族是来自两个岛屿的不同民族,诚实族的人永远说真话,而说谎族的人永远说假话。
两面族是岛屿上的一个新民族,他们的特点是说话时一句真话一句假话,真假交替。即如果第一句说的是真话,则第二句必为假话;如果第一句说的是假话,则第二句必然是真话。但第一句话到底是真是假却不得而知。
现在谜语博士碰到了3个人,这3个人分别来自3个不同的民族,诚实族、说谎族和两面族。谜语博士和这3个人分别进行了对话。
首先,谜语博士问左边的人:“中间的人是哪个族的?”,左边的人回答说:“是诚实族的”。
谜语博士又问中间的人:“你是哪个族的?”,中间的人回答说:“两面族的”。
最后,谜语博士问右边的人:“中间的人到底是哪个族的?”,右边的人回答说:“是说谎族的”。
现在请编程求出这3个人各自来自哪个族。
问题分析
这道题的特点就是变量很多,3个人,3种身份,说着3种不同的话。在分析这道题时,每个人可能会与每个人自己的想法,这里我就给出我的个人思路:
定义3个变量left
,mid
,right
,分别表示左边,中间和右边的人的种族(0:说谎族,1:诚实族,2:两面族)
诚实族总是说真话,说谎族总是说假话,而两面族说的话为不确定(有可能真,也有可能假)。既然两面族是一个不确定因素,我们就可以跳过判断两面族,直接用假设法,规定两面族是谁,然后分类讨论。
分析谜语博士的问话,会发现问题是围绕中间人的身份mid
展开的,那么我们可以通过3个人所说的话对应的mid
值进行逻辑判断,我定义了3个宏函数,分别表示3个人不为两面族(只为诚实族或说谎族)时所说的话对应的mid
值逻辑表达式。(例如:LEFT(left)
表示左边人为诚实族或说谎族时对应的mid
值逻辑表达式,left
只能为1或0 )
//三个人不是两面族时所说的话对应的mid值(中间人的身份编号),
//x为1表示真话,x为0表示谎话
#define LEFT(x) (x ? (mid == 1) : (mid != 1)) //中间的人是诚实族?
#define MID(x) (x ? (mid == 2) : (mid != 2)) //中间的人是两面族?
#define RIGHT(x) (x ? (mid == 0) : (mid != 0)) //中间的人是说谎族?
接着我们假设左边的人是两面族,即left == 2
,那么他说话可能是真,也可能是假。
下面是3个人为两面族时,说真话与说假话时对应的
mid
值的逻辑表达式:(例如:LEFT_TWO_TRUE
表示左边人为两面族且说真话时的mid
值逻辑表达式)
//三个人为两面族时所说的话对应的mid值(中间人的身份编号)
#define LEFT_TWO_TRUE (mid == 1 && mid != 2) //中间的人是诚实族,不是两面族
#define LEFT_TWO_FALSE (mid != 1 && mid != 2) //中间的人不是诚实族,也不是两面族
#define MID_TWO_TRUE (mid == 2 && mid != 2) //中间的人是两面族,又不是两面族(不可能情况)
#define MID_TWO_FALSE (mid != 2 && mid != 2) //中间的人不是两面族
#define RIGHT_TWO_TRUE (mid == 0 && mid != 2) //中间的人是说谎族,不是两面族
#define RIGHT_TWO_FALSE (mid != 0 && mid != 2) //中间的人不是说谎族,也不是两面族
左边人(两面族)说真话时,对应的mid
值逻辑就为LEFT_TWO_TRUE
,即(mid == 1 && mid != 2)
,当这个逻辑值和MID(mid)
,RIGHT(right)
都为真时(MID(mid)
,RIGHT(right)
是上面介绍的宏函数,对应中间人和右边人说话逻辑表达式),即表示3个人的身份和他们所说的话是合理的(逻辑相通),对应的逻辑表达式:(MID(mid) && RIGHT(right) && LEFT_TWO_TRUE)
两面族的特点就是可能说真话,也可能说假话,所以逻辑判断中还要或
上左边人(两面族)说谎时的情况:(MID(mid) && RIGHT(right) && LEFT_TWO_FALSE)
。现在逻辑表达式为
(MID(mid) && RIGHT(right) && LEFT_TWO_TRUE) || (MID(mid) && RIGHT(right) && LEFT_TWO_FALSE)
“3个人身份与回答的对应逻辑为真”这只是题目的其中一个条件,题目还要求3个人身份都不相同。现在已经指定了左边人为两面族,那么还会出现一种情况————中间人和右边是同族人。为了避免这种情况,我们需要将刚才的逻辑结果与
上(mid + right == 1)
,即中间人和右边人只有一个诚实族。
最后,当假设左边人为两面族时,有以下逻辑判断:
((MID(mid) && RIGHT(right) && LEFT_TWO_TRUE) ||
(MID(mid) && RIGHT(right) && LEFT_TWO_FALSE)) &&
(mid + right == 1)
同理,当中间人为两面族时的逻辑判断为:
((LEFT(left) && RIGHT(right) && MID_TWO_TRUE) ||
(LEFT(left) && RIGHT(right) && MID_TWO_FALSE)) &&
(left + right == 1)
当右边人为两面族时的逻辑判断为:
((LEFT(left) && MID(mid) && RIGHT_TWO_TRUE) ||
(LEFT(left) && MID(mid) && RIGHT_TWO_FALSE)) &&
(left + mid == 1)
得出了逻辑表达式的判断条件,我们还需要通过穷举法列举所有可能,三种假设需要分开穷举,假设左边人left
为两面族,就要遍历mid
和right
,判断他们是诚实族或说谎族;假设中间人mid
为两面族,就要遍历left
和right
;假设右边人right
为两面族,就要遍历left
和mid
。
代码实现
#include <stdio.h>
#include <string.h>
//三个人不是两面族时所说的话对应的mid值(中间人的身份编号),
//x为1表示真话,x为0表示谎话
#define LEFT(x) (x ? (mid == 1) : (mid != 1)) //中间的人是诚实族?
#define MID(x) (x ? (mid == 2) : (mid != 2)) //中间的人是两面族?
#define RIGHT(x) (x ? (mid == 0) : (mid != 0)) //中间的人是说谎族?
//三个人为两面族时所说的话对应的mid值(中间人的身份编号)
#define LEFT_TWO_TRUE (mid == 1 && mid != 2) //中间的人是诚实族,不是两面族
#define LEFT_TWO_FALSE (mid != 1 && mid != 2) //中间的人不是诚实族,也不是两面族
#define MID_TWO_TRUE (mid == 2 && mid != 2) //中间的人是两面族,又不是两面族(不可能情况)
#define MID_TWO_FALSE (mid != 2 && mid != 2) //中间的人不是两面族
#define RIGHT_TWO_TRUE (mid == 0 && mid != 2) //中间的人是说谎族,不是两面族
#define RIGHT_TWO_FALSE (mid != 0 && mid != 2) //中间的人不是说谎族,也不是两面族
char race[20]; //种族名称,用于输出结果
//函数声明————获取种族名称
char *Get_Race(int i, char *race);
int main()
{
//三个人的身份编号,0:说谎族,1:诚实族,2:两面族
int left = 0, mid = 0, right = 0; //左、中、右
//假设左边的人是两面族
left = 2;
for(mid = 0; mid <= 1; mid++)
for(right = 0; right <= 1; right++)
if(((MID(mid) && RIGHT(right) && LEFT_TWO_TRUE) ||
(MID(mid) && RIGHT(right) && LEFT_TWO_FALSE)) &&
(mid + right == 1))
goto RESULT;
//假设中间的人是两面族
mid = 2;
for(left = 0; left <= 1; left++)
for(right = 0; right <= 1; right++)
if(((LEFT(left) && RIGHT(right) && MID_TWO_TRUE) ||
(LEFT(left) && RIGHT(right) && MID_TWO_FALSE)) &&
(left + right == 1))
goto RESULT;
//假设右边的人是两面族
right = 2;
for(left = 0; left <= 1; left++)
for(mid = 0; mid <= 1; mid++)
if(((LEFT(left) && MID(mid) && RIGHT_TWO_TRUE) ||
(LEFT(left) && MID(mid) && RIGHT_TWO_FALSE)) &&
(left + mid == 1))
goto RESULT;
//没找到答案
printf("error\n");
return 0;
//打印结果
RESULT:
printf("左边的人的种族是%s\n", Get_Race(left, race));
printf("中间的人的种族是%s\n", Get_Race(mid, race));
printf("右边的人的种族是%s\n", Get_Race(right, race));
return 0;
}
/**
* @brief 通过编号获取种族名称
* @param i 种族编号
* @param race 种族名称
* @return 种族名称(字符串)
*/
char *Get_Race(int i, char *race)
{
switch(i)
{
case 0: strcpy(race, "说谎族");break;
case 1: strcpy(race, "诚实族");break;
case 2: strcpy(race, "两面族");break;
}
return race;
}
运行结果
网上参考
原文链接:http://c.biancheng.net/cpp/html/3356.html
部分思路:
变量定义
第一个条件
我比较喜欢我自己的推理方法(或许大家都比较认可自己的想法),上面这个思路几乎把所有情况都推理了一遍,都不需要用代码来穷举了,个人觉得没必要。感兴趣的朋友可以去原文查看该作者的完整分析过程。
虽然他推理的内容很多,但代码比我简洁。
#include<stdio.h>
int main ()
{
int L, M, R, LL, MM, RR;
for(L=0; L<=1; L++)
for (M=0; M<=1; M++)
for(R=0; R<=1; R++)
for(LL=0; LL<=1; LL++)
for(MM=0; MM<=1; MM++)
for(RR=0; RR<=1; RR++)
if( (L && !LL && M && !MM || !L && !M) &&
!M &&
(R && !M && !MM || (RR && !R) ||
(!R && !RR && (M||MM)) ) &&
L+LL!=2 &&
M+MM!=2 &&
R+RR!=2 &&
L+M+R==1 &&
LL+MM+RR==1
)
{
printf("左边的人来自%s\n",LL?"两面族":(L?"诚实族":"说谎族"));
printf("中间的人来自%s\n",MM?"两面族":(M?"诚实族":"说谎族"));
printf("右边的人来自%s\n",RR?"两面族":(R?"诚实族":"说谎族"));
}
return 0;
}