zoukankan      html  css  js  c++  java
  • 博弈游戏

        这次要写的是一个博弈游戏,题目的链接地址是:http://acm.ustc.edu.cn/ustcoj/problem.php?id=1130。题目的大概意思是:在一个有向无环图上,每个节点有若干的石子。Alice和Bob在上面移动石子,每次可以把任意一个石子沿着一个有向边移动。两人轮流操作,如果该谁移动的时候已经没有石子可移动,那这个人就输了。

        他们约定Alice先走,而Bob负责把N个石子放在给定的有向无环图上的节点上。假设所有的石子都是相同的,那么Bob有多少种方法可以使得Alice必输。因为这个结果非常大,计算出总数对1000000007的余数就好了。有向无环图的节点数目不大于100,边数不大于10000。石子的数目不大于10000。

        1、Nim游戏。

        如果什么都不知道直接就开始做这个题目,确实有点困难。下面我们先看一下博弈游戏里面最经典的模型:NIM游戏。Alice和Bob玩过的另外一个游戏:有N堆石子。轮到哪个人操作的时候他可以从任意一堆中拿掉任意个石子。两人交替操作,如果等到谁拿的时候没有石子了那就输了。这个看起来复杂的问题其实有个简单而完美的解答:如果各堆石子数目的异或和为0,那么先手必败。否则先手必胜。

        这个是很好理解的。

        a、如果每堆石子的数目为0,那么先手肯定输了。

        b、如果石子数目并不是0但是各堆石子数目的异或和是0,那么不管Alice经过什么操作Bob都能使得异或和恢复为0,这样到最后还是Bob会赢。

        c、如果石子数目异或和不为0,那么Alice可以首先使得异或和为0,这是Bob面临的就是Alice刚才面临的状态,Alice一定会赢。

        2、必胜、必败的理解。

        从Nim游戏中可以看出,这种游戏是不存在平局的。这类型的游戏,不过布局是什么样的,一定存在必胜的策略。那我们来看看必胜状态和必败状态有什么特点:

        a、每一个必胜状态都是可以通过一步操作到达一个必败状态。

        b、每一个必败状态不过经过什么操作都只能到达一个必胜状态。

        虽然这个听起来有点绕但,是还是很好理解的。关键就是这个必败状态。如果一个人处于必败状态,那么另一个人一定处于必胜状态。而在必胜状态的人只需要经过一步操作是的现在局面的状态变成必败状态就OK了。

        再来看Nim游戏,所有的石子数目的异或和为0时为必败状态,不为0时为必胜状态。

        3、为什么异或操作可以合并多个子游戏?

        可以把每一堆石子看做一个游戏,石子数为该游戏现在的状态Pi。那么N堆石子的总状态为P=P1^P2^P3……。为了说明异或的有效性,只需要满足下面这些条件:

        a、P=0,为必败状态。

        b、P>0,总是可以到达P=0。

        c、P=0,不管怎么操作都会到到达P>0。

        这些是很好证明的。

        4、状态的计算。

        并不所有的游戏都允许玩家可以取任意个石子的。有时候计算这个值就比较复杂了。比如,只有一堆石子,每次你只能取不超过5个石子,那这时该怎么定义7个石子的状态?大家应该都是见过这种题目的。设置P=N%5就可以了。可以说明为什么这样设置可以满足:

        a、N=0,则P=0,为必败状态。

        b、N>0,为必胜状态,那么Alice取走N%P个石子就可以变成必败状态。

        c、N%5=0,为必败状态。

        这只是一个特例,那普通的状态应该怎么设定呢?如果一个状态Px可以经过操作到达状态集合P:{Pa,Pb,Pc},那么Px=P中没有出现过的最小的非负整数。其实这样操作有一个好处就是把状态值映射到最普通的取石子游戏中了。

        这里可能会有人问一个问题:假设P集合中有Pb>Px,那么现在处于这个状态的人为什么进行操作使得现在的状态变成Pb,而非得变成比Px小的状态?其实这个问题非常好理解:

        a、Pb也是他能到达状态的集合中没有出现过的最小非负整数。

        b、如果变成Pb,那么是不是把对方的优势变大?因为现在对方能控制的范围更大了?

        5、解题方法。

        通过上面的一些东西,大概也就知道怎么解决这个题目了。先来看一下这个游戏的类型:Alice和Bob是在移动石子,一个很明显的特征就是石子与石子之间是没有影响的。如果把一个石子的移动过程看做是一个游戏的话,那整个游戏就可以看做S个子游戏的和,总的状态为各个子游戏状态的Nim和。

        紧接着的一个问题是每个子游戏的状态怎么确定?每个子游戏的状态是由石子所在的位置决定的,他能到达的状态就是他的出边到达节点的状态集合中没有出现过的最小的正整数。

        可以看到题目中的石子数目还是非常大的,那么怎么来求出方法的总数?可以看到一个可以利用的小特点:在同一个节点放偶数个石子相当于没有放,因为在求Nim和的时候变成0了。那么Bob只需要把一些石子放到节点上,剩下的石子两个做一组随便放节点上放置就可以了。

        要是暴力枚举每个节点有没有石子,那么总的复杂度将是O(2^N),显然是没办法接受的。Bob真的需要关心每个节点具体有没有石子吗?他其实只要知道用掉x的石子可以有多少种方法可以使得游戏状态为0。这个显然可以是用动态规划来解决的。

        设置数组dp[x][y][z]表示为:考虑第x个位置时、有y个石子、游戏状态和为z有多少种方法。状态转移方程为:

    dp[x][y][z] += dp[x-1][y][z^nim[x]];

        这儿利用了异或操作的可逆性。虽然求出来各种状态值的总数,但是最后Bob需要的只是dp[N][y][0]。如果S-y不为偶数的话就放弃这组了,因为剩下的石子能任由Bob去放置了。设置:

    total=(S-y)/2

        那么最后的结果就是把total个相同的石子放到N个相同的盒子有多少种方法:

    result += C(total+N-1,N)*dp[N][y][0]

        result为所有方法的总数。走到了最后一步,但是我们又发现了一个问题:C(total+N-1,N)将是一个很大的数。题目的结构是求出result%1000000007。如果是没有除法的时候这个是非常简单的,但是这里的C(total+N-1,N)显然是有不少的除法的。看了是避免不了用大数运算了。现在留意一下1000000007这个数,这个是一个素数啊,那Bob可以用乘法逆元的性质把除法变成乘法。至于乘法怎么求,用扩展的欧几里得算法应该是很快的。

        这个题目的分析就到这里了。

    ---------------------------

    个人理解,欢迎拍砖。

  • 相关阅读:
    C#实现京东登录密码加密POST
    查询SQL Server数据库所有表字段备注
    DataGridView数值列和日期列
    (转)Android 系统 root 破解原理分析
    Dynamics AX 中重点数据源方法
    .NET中Debug模式与Release模式
    DotNetBar的初步使用
    省市区联动小功能
    多余的Using Namespaces或引用会影响程序的执行效率么?
    MSIL指令集
  • 原文地址:https://www.cnblogs.com/ggzwtj/p/2189631.html
Copyright © 2011-2022 走看看