Grundy值

当前状态的Grundy值就是除任意一步所能转移到的状态的Grundy值以外的最小非负整数,

以硬币问题一为例,可写成:

int init_grundy()
{
sg[] = ;
for(int i = ;i <= x;i++) //递推求前x个SG值
{
set<int>st;
for(int j = ;j < k;j++)
if(a[j] <= i) st.insert(sg[i - a[j]]); int g = ;
while(st.count(g)) g++;
sg[i] = g;
}
}

Grundy值有什么用呢?

它的作用是巨大的,利用它,不光可以解决这个问题,其它许多问题都可以转换成前面介绍的Nim问题,即问题的解等于子问题的异或和。

Nim问题为什么等于异或和之前口胡过,这些问题为什么等于子问题的Grundy值的异或和呢?

根据Grundy的定义,先看下Grundy的性质(与Nim对比):

  • Nim中有x颗石子的石子堆,能够转移成0, 1, 2, ..., x-1可石子的石子堆
  • 从Grundy值为x的状态出发,也能转移到Grundy值为0, 1, 2, ,,,,, x-1的状态

也就不难理解为什么是异或和了:当必败态为Grundy异或和为0是,能保证必败态只能变成必胜态;必胜态可以转成必败态。

为了保证Grundy值为x的状态能转移为小于x的状态,Grundy的定义设为不在子问题Grundy值中的最小值(也就是说小于x的Grundy值都存在于子问题中)//好像有点循环论证,,,醒醒,这也能叫证明,,,个人理解吧

也不难发现,Nim问是Grundy问题的特例,其单堆的Grundy值为x。

例题

1、硬币游戏2

就是个堆Grundy值得异或和,异或和为0先手必败,否则先手必胜。

2、Cutting Game

题目:有一张 $w \times h$ 个格子的长方形纸,两个人轮流切割,水平或者垂直的切成两部分,最小切出单个格子($1 \times 1$)的一方获胜。当双方都采取最佳策略时,谁会获胜?

分析:

这样会发生分割的游戏,也能够计算Grundy值。(为啥啊??)

当一张 $w \times h$ 的纸张分割成两张时,假设所得的纸张的Grundy值分别为 $g_1$ 和 $g_2$,则这两张纸对应的状态的Geundy值为 $g_1 \ XOR \ g_2$。

另外,易知,一旦切割出长或宽为1时,下一步就一定能够切出 $1 \times 1$的纸张,所以知道此时必败。因此切割纸张时总要保证长和宽至少为2.

不然,grundy(2,2) 时 st{ grundy(1,2)^grundy(1,2), grundy(2,1)^grundy(2,1) },则sg[2]=1先手必胜;而实际上先手必败。

(为什么硬币问题不要考虑转译成必败态,不懂,哪个大佬能教教我)

#include<cstdio>
#include<set>
#include<cstring>
using namespace std; const int maxw = +;
const int maxh = +;
int sg[maxw][maxh]; int grundy(int w, int h)
{
//printf("%d %d\n", w, h);
int& ret = sg[w][h];
if(ret != -) return ret;
if(w== || h==) return ret=; set<int>st;
for(int i = ;i < w-;i++) st.insert(grundy(i, h) ^ grundy(w-i, h));
for(int i = ;i < h-;i++) st.insert(grundy(w, i) ^ grundy(w, h-i));
ret = ;
while(st.count(ret)) ret++;
return ret;
} int w, h; int main()
{
memset(sg, -, sizeof(sg));
while(scanf("%d%d", &w, &h) == )
{
if(grundy(w, h)==) printf("LOSE\n");
else printf("WIN\n");
}
return ;
}

最新文章

  1. 响应式web设计总结
  2. Thread.Sleep引发ThreadAbortException异常
  3. php实现设计模式之 解释器模式
  4. Linux系统重要快捷键&amp; Shell 常用通配符
  5. Sublime插件库新成员基于APICloud快速开发跨平台App
  6. MYSQL远程登录权限设置
  7. 【spring-boot】快速构建spring-boot微框架
  8. HLS协议实现
  9. 关于win8.1&ldquo;连接被远程计算机关闭&rdquo;的一种解决方案
  10. C# 集合性能 总结
  11. javascript代码混淆原理
  12. 金山卫士开源软件之旅(十) KSafeMainproject的分析 1
  13. centos安装Chromium
  14. 小A点菜 洛谷 p1164
  15. ch2-vue实例(new Vue({}) 属性与方法 声明周期)
  16. 【转】JAVA反射与注解
  17. maven插件的使用
  18. Linux的信号解释
  19. TCP/IP Illustrated Vol1 Second Edition即英文版第二版,TCP部分个人勘误
  20. C语言:通过函数指针来完成两个数的加减乘除(函数指针当做参数使用)

热门文章

  1. fp-growth代码问题(Python)
  2. 微信小程序:防止多次点击跳转(函数节流)
  3. 通过调试vue-cli 构建代码学习vue项目构建运行过程
  4. English--七种句子成分概述
  5. springmvc注解一
  6. 使用node写爬虫入门
  7. Synchronized与ReentrantLock区别总结
  8. Cheat Engine 字节数组类型
  9. caffe库源码剖析——net层
  10. mybatis update 返回值