《算法艺术与信息学竞赛》之 递归与分治法 例三 聪明的学生

发现这个题目在大视野上有。
2523: [Ctsc2001]聪明的学生
Description

一位教授逻辑学的教授有三名非常善于推理且精于心算的学生A,B和C。有一天,教授给他们三人出了一道题:教授在每个人脑门上贴了一张纸条并告诉他们,每个人的纸条上都写了一个正整数,且某两个数的和等于第三个。于是,每个学生都能看见贴在另外两个同学头上的整数,但却看不见自己的数。

这时,教授先对学生A发问了:“你能猜出自己的数吗?”A回答:“不能。”
教授又转身问学生B:“你能猜出自己的数吗?”B想了想,也回答:“不能。”
教授再问学生C同样的问题,C思考了片刻后,摇了摇头:“不能”。
接着,教授又重新问A同样的问题,再问B和C,……,经过若干轮的提问之后,当教授再次询问某人时,此人突然露出了得意的笑容,把贴在自己头上的那个数准确无误的报了出来。

现在,如果告诉你:教授在第N次提问时,轮到回答问题的那个人猜出了贴在自己头上的数是M,你能推断出另外两个学生的头上贴的是什么数吗?

[提示]
在没有人猜出自己头上的数之前,大家对教授提问的回答始终都是“不能”;而且除此之外在A,B,C之间是没有进行任何信息交流的。也就是说,每个人推断的依据仅仅是另外两个人的头上数,以及大家对教授的提问所做出的否定回答。
教授总是从学生A开始提问的。
你可以假定,这三个足够聪明的学生能够根据已知的条件在最早的轮次猜出自己的数,并且永远都不会猜错。
稍经分析和推理,你将得出以下结论:总是头上贴着最大的那个数的人最先猜出自己头上的数。

Input

包括若干组测试数据,其中的每一行代表一组测试数据,由两个整数N和M组成(即在教授第N次提问时,轮到回答问题的那个人猜出了贴在自己头上的数是M)。两个数之间用空格分隔开。最后,由-1 -1组成的一行标志着输入数据的结束。

Output

按照输入文件中的顺序依次给出各组数据的结果。
文件中对应每组数据的输出的第一行是一个整数p,是可能情况的总数。接下来的p行,每一行包括三个数,分别为贴在A,B,C头上的三个数。输出时,所有解按照A头上的数增序排列;在A头上的数相同的情况下,按照B头上的数增序排列。

这个题刚看一点思路没有,还是看了书上的题解看了半天才理解过来。首先我们来讲解一下思路:
就拿书上的例子来说 1,2,3三人的数分别是2,8,6。然后我们按步数来,前三步,都猜不出来,第四步,1没有猜出来。
好了,现在到了第五步,2号同学因为可以看到1,3两人的数字分别是2,6,自己的数字只有可能是其和或差,那么久有可能是4或8,假设自己是4,那么在再分析之前的人:第三步时3号看到了2和4,他会觉得自己是2或6那么3号现在假设自己是2,则会让我(此处指2号)看到2和2,我(2号)如果看到了2和2,就必然猜出来自己是4(因为数不可能是0),所以3号知道了自己不是2,而是6,三号可以猜出来。
但是面对事实,极其聪明的三号并没有猜出来,这只能证明一点:我(2号)不是4,是8!
到此结束。

这个过程让人感觉非常绕。
我们抛开数据,从整体上看这个问题:每个人知道自己有两种可能,当一个人否定其中一种时,他就可以猜出来。
问题转化为如何否定自己的另一个答案。
那么就假设自己是这个答案,如果自己是这个答案会使有人在之前猜出来,那么这个答案就一定是错的!
之前那个人猜出来的情况与此人相同,故可以进行递归。
那么终止条件呢?
注意到有一类情况下,有人可以只有一种答案,就是他看到的两个人的数字相同,他自己不可能是0,故必是其和!

下面上代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
int P(int a,int b){
    return a<b?b-a:b+3-a;//求从a问到b需要的最小步数 
}
int T(int i,int j,int x,int y,int z){//递归求解 
    if(i==j)
        return z;
    else if(i>j)
        return T(j,i-j,y,z,x)+P(x,z);
    else
        return T(i,j-i,x,z,y)+P(y,z);
}
int main(){
    while(scanf("%d%d",&n,&m)&&n!=-1){
        int k=n%3,x,y;
        int a[m+1][4],cnt=0;
        if(k==0)
            x=1,y=2,k=3;
        else if(k==2)
            x=1,y=3;
        else x=2,y=3;//处理输入后的顺序 
        for(int i=1;i<m;i++)//枚举求值 
            if(T(i,m-i,x,y,k)==n){
                a[++cnt][x]=i;//将答案储存在a数组中 
                a[cnt][y]=m-i;
                a[cnt][k]=m;
            }
        printf("%d\n",cnt);
        for(int i=1;i<=cnt;i++)//输出 
            printf("%d %d %d\n",a[i][1],a[i][2],a[i][3]);
    }
    return 0;
}

本题非常考验思维,希望读者朋友们最好自己顺着来一遍。以上题解是参考了书上的思路,代码为个人原创,如有不足望及时指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值