题目:
Description
zf每年都会和zn玩一个游戏:在1到1000以内随机选n个硬币,让zf先开始他的回合,然后互相轮转回合。zn如果输了就会给zf n个硬币。当然是zf先开始游戏啦。
比如选的数是4,那么zf是赢家。因为他可以取掉一半,让硬币堆大小变为2,然后无论zn选了两个操作中的任何一个结果都变成了1,这时候zf去掉最后一个,赢得游戏。所以选到数字4的时候zf相当于已经胸有4块钱了。
在各自的回合中都能执行一次操作:让硬币数变为原来的一半(向下取整)或者减一。谁清除掉最后一枚硬币谁就赢了。
zn和zf都很聪明,每一次都会选择最有利的方式进行操作
如果进行了1000轮游戏,请问zf骗到硬币数的期望是多少?
Input
无输入
Output
输出一个数字表示1000轮游戏后zf骗到硬币数的期望;
可以提交生成这个数据的代码,当然也可以直接提交答案。
Sample Input
无
Sample Output
无
问题分析:
因为这是比赛的第一题,所以觉得应该不会那么难把哈哈哈,结果看了几遍还是蒙的,题目也有几个歧义点。
既然题目问的是期望,那么其实每一次骗到硬币的期望应该是一样的,这样子最后x1000即可。
再深入思考一下,也就是说从1到1000的硬币,只要确定了硬币数(因为已经确定了先手是zf了),而zn和zf都很聪明,每一次都会选择最有利的方式进行操作,那么结果就是唯一确定的,无法改变的(因为两个人每一次选择都会选择最有利的操作方式hhh好好理解一下,很微妙)。
那么,我们只要把1到1000硬币的结果打表,然后求出每一次的期望,再乘1000即可。(思考的时候是顺式的,但做题步骤是反式的)。
关于什么是打表。简单科普一下:(以下内容摘自百度)
打表,是一个信息学专用术语,意指对一些题目,通过打表技巧获得一个有序表或常量表,来执行程序某一部分,优化时间复杂度。这种算法也可用于在对某种题目没有最优解法时,用来得到分数的一种策略。
打表一般步骤
找到答案的方式
一、通过暴力搜索,找出对于数据的答案,适用于数据较大,题目简单的情况;
二、通过手算,找出每个数据的答案,适用于数据较小且题目较难的情况;
三、在某些题目中,因为考虑到预处理出所有答案的时间复杂度可能会比依次读入再求更优,所以就在读入数据前进行对所有可能的询问的答案或部分必要条件的预处理。这种方法虽然也是打表,但编程复杂度不亚于其他程序,而且一般是题目的正解。
输出答案的方式
一、直接在程序内打表,如果打表复杂度较大则不可用。
二、提前打表,然后复制放入程序。
在打表的时候我们就要判断每个硬币数量的结果,从1开始:
因为zf是先手,若硬币数为1则zf必赢(直接减一即可)。
1 | √ | 因为zf是先手,若硬币数为1则zf必赢(直接减一即可)。 |
---|---|---|
2 | × | 因为zf是先手,每一轮必进行操作,无论是除2还是-1都会让对方取得1的先手 |
3 | √ | 除2后得1,输;减一后得2,赢;因为两人都很聪明,故会选后者 |
4 | √ | 除2后得2,赢;减一后得3,输; |
5 | √ | 除2后得2,赢;减一后得4,输; |
故,上面打了5个数据的表,我们可以得知,硬币数的操作只能是除2或-1,如5,只有2和4的结果,然后对应回表中我们已经得出的答案,若2和4均为√则输,否则为赢(因为zf会选择最有利的)。
这道题比赛时没A出来,思路也刚好到这里,然后我就犯了一个错误,手算…导致算错了。真的有点难过,与AC擦肩而过。
其实可以编写代码让计算机帮我们算,如下:
AC代码:
#include <stdio.h>
//全局变量自动初始化为0,0即√,1即×
int a[1001];
int main()
{
//把5以内的先打表,其中只有2是×
a[2] = 1;
int i;
int sum = 0;
//标记完全部的硬币数情况
for ( i=6; i<=1000; i++ )
{
if ( a[i-1] == 0 && a[i/2] == 0 )
a[i] = 1;
}
for ( i=1; i<=1000; i++ )
{
if ( a[i]==0 )
{
sum += i;
}
}
printf ( "%d", sum );
return 0;
}