【逻辑推理+DP】BZOJ2523(Ctsc2001)[聪明的学生]题解

题目概述

有3个学生A,B,C,每个学生头上都贴了一个正整数,刚开始询问A是否猜出自己头上的正整数,如果没猜出,询问B,如果没猜出,询问C,如果没猜出,询问A……n次之后一个学生猜出了自己的正整数为m,求所有可能的组合。

解题报告

ps:之前就看到过这道题目,但是我那时候是普及组蒟蒻(现在是提高组蒟蒻=_=),直接望而却步无视掉了

这种逻辑题目,最重要是先看懂题目(没错我就没看懂)。刚开始毫无头绪,感觉不停回答不知道和推理没有任何关系啊……直到我发现这么一组数据:2 1 1。学生A看到学生B和学生C都是1,而1-1=0不是正整数,那么学生A就知道了,他不是学生B和学生C的差,而是他们的和1+1=2,从而猜出自己的数字。

题目里的提示非常重要,特别是“最大的人肯定先猜到”这个提示(哪里稍微分析和推理就能推出来啊QAQ,完全不会推啊)。那么对于(x,y,x+y)这个三元组,x+y这个人肯定先猜到,但是他是如何推理出他不是|x-y|呢?

我们可以先算出(x,y,|x-y|)的最少提问次数,如果在(x,y,|x-y|)的最少提问次数内还是没有人猜出来,x+y就知道他不是|x-y|了(否则另一个比|x-y|大的人肯定猜出来了,因为大的先猜出),从而得知他是x+y。而求出(x,y,|x-y|)其实是一个递归过程,所以我们可以用记忆化搜索(或者递推)。

网上大神们都把(x,y,x+y)改为(x,y,t)表示最大的人在t∈{0,1,2},t右边的人是x,t左边的人是y,从而精简转移方程(都太强了Orz):

x=y:t //走t步到达t,所以为t
x<y:f[y-x][x][(t+2)%3]+1 //(t+2)%3走一步到达t,所以+1
x>y:f[y][x-y][(t+1)%3]+2 //(t+1)%3走两步到达t,所以+2

然而我们会发现f根本开不下!这怎么办?由于题目里n<500,所以我们莫不如不要求出f[x][y][t],而是带着n一起处理,每次转移就n-1或n-2,直到n<=0或x=y,此时如果x=y且n=t,就说明f[x][y][t]=n,反之f[x][y][t]!=n。这样就把时间和空间都削减了,原来的记忆化搜索反而是累赘。

示例程序

#include<cstdio>
#include<cstring>
using namespace std;
const int maxm=30000;

int n,m;
int ans,who[maxm+5][3];

bool check(int x,int y,int t,int now)
{
    while (now>0)
    {
        if (x==y) break;int xx=x,yy=y;
        if (x<y) x=yy-xx,y=xx,t=(t+2)%3,now--; else
        x=yy,y=xx-yy,t=(t+1)%3,now-=2;
    }
    if (x==y) return now==t;
    return false;
}
int main()
{
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    for (scanf("%d%d",&n,&m);~n&&~m;scanf("%d%d",&n,&m))
    {
        n--;int a=n%3,b=(a+1)%3,c=(b+1)%3;ans=0;
        for (int i=1;i<=m-1;i++) if (check(i,m-i,a,n))
             ans++,who[ans][a]=m,who[ans][b]=i,who[ans][c]=m-i;
        //枚举所有情况并验证
        printf("%d\n",ans);
        if (a==1)
        {
            for (int i=ans;i>=1;i--)
                printf("%d %d %d\n",who[i][0],who[i][1],who[i][2]);
        } else
        {
            for (int i=1;i<=ans;i++)
                printf("%d %d %d\n",who[i][0],who[i][1],who[i][2]);
        }
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值