题目

题目大意

在一个1*n的棋盘上,有黑棋和白棋交错分布,每次,一个人可以移动自己的ddd颗旗子。

问先手必胜的方案数。


思考历程

在一开始,我就有点要放弃的念头。

因为这题是一道博弈问题。

我是非常不擅长博弈类问题的。

但是其他的题有想不出来,于是只能硬是想了好久。

最终那了个部分分。


正解

这题正解当然跟博弈有一些关系。

首先,对于这题有一个隐藏的限制。

白棋不能向左移,黑棋不能向右移。

这是为什么?

我们可以感性理解一下(证明什么的我当然不会):

如果白棋左移,那么在它右边的黑棋可以模仿它的操作,也跟着左移。

同理,如果黑棋右移,那么在它左边的白棋也可以模仿他的动作,也跟着右移。

那么我们就可以发现,这样走是没有意义的。

然后我们就可以转换问题的模型:

可以将对应(相邻)的两个白棋和黑棋中间的距离当作石子。

那么就有K2\frac{K}{2}2K​堆石子,然后每次可以在至多ddd堆石子,至少111堆石子中各自拿走一颗石子。

这个问题叫Nimk问题。

这又是个什么东西啊?

对于Nimk问题,我们有一个神奇的结论:将所有堆石子的个数化成二进制,每一位统计一下111的个数,如果个数都是d+1d+1d+1的倍数,那么这就是必败态。

如何证明?

我们可以感性理解一下:怎么又是感性理解……

先手只能拿走111到ddd堆中的石子,那么,后手可以故意的模仿,然后将两人取得的石子的总数固定在d+1d+1d+1。那么如果一直这么下去,后手必定会赢。

为什么要转成二进制呢?

我觉得,其实只要转成了二进制,那么后手就能保证每次能够取得这么多的石子,使得两个人取石子的总数为d+1d+1d+1。具体怎样解释,我觉得我还要好好理解一下,暂且感性感性吧(感性理解真是一个好东西)。

模型转换成了Nimk问题,让我们求先手必胜的方案数。

先手必胜的方案数等于总方案数减去先手必败的方案数。

如何求先手必败的方案数呢?

当然是DP。

设fi,jf_{i,j}fi,j​表示在二进制的前iii位中,一共用了jjj颗石子的先手必败的方案数。

仔细想想这个状态没有任何问题,因为我们只需要保证在每一个二进制位上都满足它必败就好了。这样设状态之后转移就方便多了。

我们再枚举一个kkk(小写的kkk,不要混淆),表示一共有k∗(d+1)k*(d+1)k∗(d+1)堆石子下一个二进制位上为111。

转移?

fi+1,j+k∗(d+1)∗2i←fi,j∗CK2k∗(d+1)f_{i+1,j+k*(d+1)*2^i}\leftarrow f_{i,j}*C_{\frac{K}{2}}^{k*(d+1)}fi+1,j+k∗(d+1)∗2i​←fi,j​∗C2K​k∗(d+1)​

注意:为了程序实现方便,在这个方程中,前iii位的最高位实际上是i−1i-1i−1位。这样的DP中,初始化直接是f0,0=1f_{0,0}=1f0,0​=1

在一波DP之后,我们就可以统计答案了。

其实我们可以将其视为,在n−j−Kn-j-Kn−j−K个空中插入K/2K/2K/2个东西。

这个东西可以用组合数来搞一搞,那么就是Cn−j−K2K2C_{n-j-\frac{K}{2}}^{\frac{K}{2}}Cn−j−2K​2K​​种方案,用它来乘上fMAXBIT,jf_{MAXBIT,j}fMAXBIT,j​就好了。

然后这题就愉快地解决了。

复杂度表示懒得分析。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define mo 1000000007
int n,K,d;
int C[10001][101];
int f[17][10001];
int main(){
scanf("%d%d%d",&n,&K,&d);
C[0][0]=1;
for (int i=1;i<=n;++i){
C[i][0]=1;
for (int j=1;j<=i && j<=K;++j)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mo;
}
f[0][0]=1;
for (int i=0;i<15;++i)
for (int j=0;j<=n-K;++j)
if (f[i][j])
for (int k=0;k*(d+1)<=K>>1 && j+k*(d+1)*(1<<i)<=n-K;++k)
(f[i+1][j+k*(d+1)*(1<<i)]+=(long long)f[i][j]*C[K>>1][k*(d+1)]%mo)%=mo;
int ans=0;
for (int j=0;j<=n-K;++j)
ans=(ans+(long long)f[15][j]*C[n-j-(K>>1)][K>>1]%mo)%mo;
printf("%d\n",(C[n][K]-ans+mo)%mo);
return 0;
}

我认为这个程序实现不用注释。


总结

博弈类问题,可真是一个神奇东西啊!

然而,好多的我都不会严谨证明。

感性理解就好……

我觉得以后要多做一些博弈类问题。

最新文章

  1. JAVA中的字符串操作
  2. js对象私有变量公有变量问题
  3. Linux下查看某一进程所占用内存的方法
  4. 【56测试】【字符串】【dp】【记忆化搜索】【数论】
  5. Snippet: align a TextView around an image
  6. FlashInspector 【Firefox浏览器插件,flash分析工具】
  7. RabbitMQ学习总结 第六篇:Topic类型的exchange
  8. 毕向东Java基础:day09_3-4
  9. sql中获得时间的参数
  10. MyEclipse 2014 + JSP+ Servlet
  11. UVa 10288 (期望) Coupons
  12. 导出Excel帮助类
  13. To Build A Dev Env On Linux(Ubuntu)
  14. Spiral Matrix II 解答
  15. MarkDown本地图片上传工具制作总结
  16. AIX逻辑卷扩容
  17. &lt;算法&gt;进制转换超详细
  18. 破解附近寝室的Wifi密码
  19. EBS-子库存转移和物料搬运单区别
  20. ESXi 嵌套KVM虚拟化 配置

热门文章

  1. String str = new String(&quot;abc&quot;),这段代码一共生成了几个String对象?为什么?
  2. csp-s模拟9697题解
  3. Kubernetes的包管理工具Helm的安装和使用
  4. HTML5能取代Android和iOS应用程序吗?
  5. el-table单元格新增、编辑、删除功能
  6. 云-腾讯云-实时音视频:实时音视频(TRTC)
  7. iOS开发之SceneKit框架--SCNCamera.h
  8. POJ - 2406 ~SPOJ - REPEATS~POJ - 3693 后缀数组求解重复字串问题
  9. Python学习之文件操作(二)
  10. iOS开发系列-线程状态