C语言每日一练——第75天:谜语博士的难题(二)

C语言每日一练
2022年1月14日

题目描述

已知:诚实族和说谎族是来自两个岛屿的不同民族,诚实族的人永远说真话,而说谎族的人永远说假话。
两面族是岛屿上的一个新民族,他们的特点是说话时一句真话一句假话,真假交替。即如果第一句说的是真话,则第二句必为假话;如果第一句说的是假话,则第二句必然是真话。但第一句话到底是真是假却不得而知。


现在谜语博士碰到了3个人,这3个人分别来自3个不同的民族,诚实族、说谎族和两面族。谜语博士和这3个人分别进行了对话。

首先,谜语博士问左边的人:“中间的人是哪个族的?”,左边的人回答说:“是诚实族的”。
谜语博士又问中间的人:“你是哪个族的?”,中间的人回答说:“两面族的”。
最后,谜语博士问右边的人:“中间的人到底是哪个族的?”,右边的人回答说:“是说谎族的”。

现在请编程求出这3个人各自来自哪个族。

问题分析

这道题的特点就是变量很多,3个人,3种身份,说着3种不同的话。在分析这道题时,每个人可能会与每个人自己的想法,这里我就给出我的个人思路:

定义3个变量leftmidright,分别表示左边,中间和右边的人的种族(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为两面族,就要遍历midright,判断他们是诚实族或说谎族;假设中间人mid为两面族,就要遍历leftright;假设右边人right为两面族,就要遍历leftmid

代码实现

#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;
}
  • 48
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 46
    评论
评论 46
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小辉_Super

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值