Codeforces Round #188 (Div. 1) Game with Powers 引发的联想 NIM游戏 Sprague Grundy Theory

这是由Codeforces Round #188 (Div. 1) Game with Powers这道题引发的思考,一开始疏忽,考虑错了问题,把问题引向了NIM游戏,歪打正着想到了NIM游戏的必胜策略证明,

后来意识到虽然问题考虑错了,(也没能解决这个问题)但确实这两者还是有很大联系,我觉能能觉察到两者联系已经算成功了,查看了Codeforces上的Tutorial之后,才真正理解这类问题的本质:Sprague Grundy Theory。

题目链接:http://codeforces.com/problemset/problem/317/D

NIM问题的太过经典,网上均可找到。

首先这题题意是给定数字n,对于区间1--n中的数我们进行游戏,选手交替取走数字,一旦取走x,那么x的正整数幂次在1--n区间里的数都将被取走,如取了4,那么16,64。。。

都不能再取,无法操作判负。为了下面系统总结这类博弈问题,我们直接引用博弈论中对这类游戏的系统定理,名称:Impartial Combinatorial Games”(以下简称ICG)(公正的

组合游戏)定义如下:1、有两名选手;2、两名选手交替对游戏进行移动(move),每次一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动;3、对于游戏的

任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、骰子的点数或者其它什么因素; 4、如果轮到某名选手移动,且这

个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。根据这个定义,很多日常的游戏并非ICG。例如象棋就不满足条件3,因为红方只能移动红子,黑方

只能移动黑子,合法的移动集合取决于轮到哪名选手操作。

现在这个问题是一个ICG。

一开始我比较粗心,错误的认为取走了9,那么27,81,。。。都不能取了,这样的话,我们对于每个n,将区间1--n中的数按幂的最小底数分开,{2,4,8。。。},{3,9,

27。。。}, {5, 25, 125。。。},{6,36,216,。。。}。。。这样,就得到了好多堆石子,每次取的操作取决于取走的x,例如取走4,那么4的组内在4后面的全部被取

走,这个数字4完全取决于选手意愿,这和NIM游戏取走任意个石子(至少1个)是等价的,于是问题转化为把1--N按幂的底数分组再用NIM游戏必胜策略求解即可。这虽然是

错误思路下的结果,但是却是一个正确的思考,因为我们离本质不远了。

事实上这题不能按我那样取,因为取走9,只会同时取走81,729。。。但是这和取走9,27,81,243,729是一样的性质,取走的数目只取决于选择的数字,所以还是一个

NIM游戏(以下思路来之Codeforces官方Tutorial)于是定义一个x是simple的 如果满足:(。。。。条件见下文),按其幂分组,即可化为NIM问题。关键是求每组里元素个数。

lets call number x simple (andcorresponding sequence Pow(x) simple), if it is not apower of any number y < x. Then: 1. for each   there is simple x, such that  ; 2. for each simple distinct x and ysets Pow(x) and P(y) do not intersect. Indeed, for agiven k there is exactly one simple x such that k is power of x. This may be showed from fundamentaltheorem of arithmetic(算数基本定理): if   and d = gcd1, α2, ..., αn), then  .

Hence set [1..n] decomposes into primitive sets Pow(x), on each of those Pow(x) game proceeds independetly. Now one may use Sprague–Grundytheory to see that mexx of our game is just xor of all mexx of games onsimple Pow(x). For fixed x, if Pow(x) = {x, x2, ..., xs}, then mexx of the game on Pow(x) depends only on s, but not on x. In our case s runs from 1 to 29, mexx for all such s may be directly precalculated.Now it is enough to find numbers q(s) of simple x with a given size of Pow(x) equal to s (actually we are interested in parity(奇偶性) of q(s) not in q(s)itself).

Our constraints allows to do it directly: run x from 2 to  , if it is not crossed out, determine the size Pow(x) [increment corresponding q(s)], and cross out all numbers from Pow(x).

This cycle finds all simple sequences of length at least 2. Quantity of non-crossed numbers isthe number of all simple sequences of length 1. Now it is enough to look atparities of q(s) and xor coressponding mexx.

However one may find all q(s) for  . Indeed lets look at the number   and sequence {x, x2, x3, x4, ..., xs}. This sequence is not simple iff it is containd in somelarger simple sequence. But number of subsequenes of size s in a given sequence ofsize t > s is easy to find: it isjust [t / s]. Recalling that simple sequences do not intersect one getsthe reccurent formula:

Now it is easy to find all q(s) from q(29) to q(1).

Remark: while coding it is important toremeber simple number x = 1.

链接:http://codeforces.ru/blog/entry/7956

此处贴上tourist大神的代码:

#include <iostream>
#include <iomanip>
#include <stdio.h>
#include <set>
#include <vector>
#include <map>
#include <cmath>
#include <algorithm>
#include <memory.h>
#include <string>
#include <sstream>
using namespace std;

const int g[30] = {0,1,2,1,4,3,2,1,5,6,2,1,8,7,5,9,8,7,3,4,7,4,2,1,10,9,3,6,11,12};

bool was[66666];

int main() {
  int n;
  cin >> n;
  int ans = 0;
  int tot = n;
  for (int u=2;u*u<=n;u++) was[u] = false;
  for (int u=2;u*u<=n;u++) {
    if (was[u]) continue;
    was[u] = true;
    int x = u, y = 1;
    while ((long long)x*u <= n) {
      x *= u;
      if ((long long)x*x <= n) was[x] = true;
      y++;
    }
    ans ^= g[y];
    tot -= y;
  }
  if (tot & 1) ans ^= 1;
  if (ans > 0) cout << "Vasya" << endl;
  else cout << "Petya" << endl;
  return 0;
}

贴个自己的代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<stack>
#include<deque>
#include<set>
#include<vector>
#include<iomanip>
#include<cctype>
#include<string>
#include<memory>
#include<map>
#include<sstream>
#define mem(a) memset(a, 0, sizeof(a))
typedef long long LL;
typedef double dou;
const int Mod = 1000000007;
const double eps = 1e-8;
const LL inf = 1e18;
const int inf1 = 1e9;
using namespace std;
LL g[30] = {0,1,2,1,4,3,2,1,5,6,2,1,8,7,5,9,8,7,3,4,7,4,2,1,10,9,3,6,11,12}, c[30];

int main()
{
        LL n, i, j, ans = 0;

        cin >> n;
        for (i = 29; i >= 2; --i)
        {
                c[i] = (LL) pow((dou) n, 1.0 / i) - 1;
                for (j = i + 1; j < 30; ++j)
                        c[i] -= c[j] * (j / i);
        }
        for (i = 29, c[1] = n; i > 1; --i)
                c[1] -= c[i] * i;
        for (i = 1; i < 30; ++i)
                if (c[i] & 1)
                        ans ^= g[i];
        if (ans == 0)
                cout << "Petya\n";
        else
                cout << "Vasya\n";
        
        return 0;
}

所以问题其实还是一个NIM游戏,总结如下:

1,结论(Bouton's Theorem):对于一个NIM游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。

2,NIM游戏必胜策略证明:

定义P-position和N-position,其中P代表Previous,N代表Next。直观的说,上一次move的人有必胜策略的局面是P-position,也就是“后手可保证必胜”或者“先手必败”,现在

轮到move的人有必胜策略的局面是N-position,也就是“先手可保证必胜”。更严谨的定义是:1.无法进行任何移动的局面(也就是terminal position)是P-position;2.可以移动

到P-position的局面是N-position;3.所有移动都导致N-position的局面是P-position。

比如NIM游戏只有两堆石子,且两堆石子数量相等时后手有必胜策略,也就是这是一个P-position,下面我们依靠定义证明一下(3,3)是一个P是一个P是一个P-position。首先

(3,3)的子局面(也就是通过合法移动可以导致的局面)有(0,3)(1,3)(2,3)(显然交换石子堆的位置不影响其性质,所以把(x,y)和(y,x)看成同一种局面),只需要计算出这三种局面

的性质就可以了。 (0,3)的子局面有(0,0)、(0,1)、(0,2),其中(0,0)显然是P-position,所以(0,3)是N-position(只要找到一个是P-position的子局面就能说明是N-position)。

(1,3)的后继中(1,1)是P-position(因为(1,1)的唯一子局面(0,1)是N-position),所以(1,3)也是N-position。同样可以证明(2,3)是N-position。所以(3,3)的所有子局面都是N-

position,它就是P-position。通过一点简单的数学归纳,可以严格的证明“有两堆石子时的局面是P-position当且仅当这两堆石子的数目相等”。

根据定义,证明一种判断position的性质的方法的正确性,只需证明三个命题: 1、这个判断将所有terminal position判为P-position;2、根据这个判断被判为N-position的局

面一定可以移动到某个P-position;3、根据这个判断被判为P-position的局面无法移动到某个P-position。

第一个命题显然,terminal position只有一个,就是全0,异或仍然是0。

第二个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个

ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai'=ai^k,此时

a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。

第三个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。因为异或运算满足消去率,

a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以将ai改变成ai'不是一个合法的移动。证毕。

根据这个定理,我们可以在O(n)的时间内判断一个NIM的局面的性质,且如果它是N-position,也可以在O(n)的时间内找到所有的必胜策略。NIM问题就基本上完美的解决。

3,拓展:Sprague Grundy Theory

如果把Nim的规则略加改变,有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗……这时看上去问题复

杂了很多,该如何求解呢?

现在我们来研究一个看上去似乎更为一般的游戏:给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。事实

上,这个游戏可以认为是所有Impartial Combinatorial Games的抽象模型。也就是说,任何一个ICG都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有

向边来抽象成这个“有向图游戏”。下面我们就在有向无环图的顶点上定义Sprague-Garundy函数。

首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{空集}=0。

对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Garundy函数g如下:g(x)=mex{ g(y) | y是x的后继 }。

来看一下SG函数的性质。首先,所有的terminal position所对应的顶点,也就是没有出边的顶点,其SG值为0,因为它的后继集合是空集。然后对于一个g(x)=0的顶点x,它

所有后继y都满足g(y)!=0(可能没有后继)。对于一个g(x)!=0的顶点,必定存在一个后继y满足g(y)=0。

以上这三句话表明,顶点x所代表的postion是P-position当且仅当g(x)=0(跟P-positioin/N-position的定义的那三句话是完全对应的)。我们通过计算有向无环图的每个顶点的

SG值,就可以对每种局面找到必胜策略了。但SG函数的用途远没有这样简单。如果将有向图游戏变复杂一点,比如说,有向图上并不是只有一枚棋子,而是有n枚棋子,每次

可以任选一颗进行移动,这时,怎样找到必胜策略呢?

让我们再来考虑一下顶点的SG值的意义。当g(x)=k时,表明对于任意一个0<=i<k,都存在x的一个后继y满足g(y)=i。也就是说,当某枚棋子的SG值是k时,我们可以把它变成0

、变成1、……、变成k-1,但绝对不能保持k不变。不知道你能不能根据这个联想到Nim游戏,Nim游戏的规则就是:每次选择一堆数量为k的石子,可以把它变成0、变成1、

……、变成k-1,但绝对不能保持k不变。这表明,如果将n枚棋子所在的顶点的SG值看作n堆相应数量的石子,那么这个Nim游戏的每个必胜策略都对应于原来这n枚棋子的必胜

策略!

对于n个棋子,设它们对应的顶点的SG值分别为(a1,a2,…,an),再设局面(a1,a2,…,an)时的Nim游戏的一种必胜策略是把ai变成k,那么原游戏的一种必胜策略就是把第i枚棋子

移动到一个SG值为k的顶点。这听上去有点过于神奇——怎么绕了一圈又回到Nim游戏上了。

其实我们还是只要证明这种多棋子的有向图游戏的局面是P-position当且仅当所有棋子所在的位置的SG函数的异或为0。这个证明与上节的Bouton’sTheorem几乎是完全相同

的,只需要适当的改几个名词就行了。这里认为n枚棋子是在一个有向图上移动。但如果不是在一个有向图上,而是每个棋子在一个有向图上,每次可以任选一个棋子(也就是

任选一个有向图)进行移动,这样也不会给结论带来任何变化。

所以我们可以定义有向图游戏的和(Sum of Graph Games):设G1、G2、……、Gn是n个有向图游戏,定义游戏G是G1、G2、……、Gn的和(Sum),游戏G的移动规则是:任

选一个子游戏Gi并移动上面的棋子。Sprague-Grundy Theorem就是:g(G)=g(G1)^g(G2)^…^g(Gn)。也就是说,游戏的和的SG函数值是它的所有子游戏的SG函数值的异或。

再考虑在本文一开头的一句话:任何一个ICG都可以抽象成一个有向图游戏。所以“SG函数”和“游戏的和”的概念就不是局限于有向图游戏。我们给每个ICG的每个position定义

SG值,也可以定义n个ICG的和。所以说当我们面对由n个游戏组合成的一个游戏时,只需对于每个游戏找出求它的每个局面的SG值的方法,就可以把这些SG值全部看成Nim的

石子堆,然后依照找Nim的必胜策略的方法来找这个游戏的必胜策略了!

回到拓展的那个问题,有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗……我们可以把它看作3个子

游戏,第1个子游戏只有一堆石子,每次可以取1、2、3颗,很容易看出x颗石子的局面的SG值是x%4。第2个子游戏也是只有一堆石子,每次可以取奇数颗,经过简单的画图可

以知道这个游戏有x颗石子时的SG值是x%2。第3个游戏有n-2堆石子,就是一个Nim游戏。对于原游戏的每个局面,把三个子游戏的SG值异或一下就得到了整个游戏的SG值,

然后就可以根据这个SG值判断是否有必胜策略以及做出决策了。其实看作3个子游戏还是保守了些,干脆看作n个子游戏,其中第1、2个子游戏如上所述,第3个及以后的子游

戏都是“1堆石子,每次取几颗都可以”,称为“任取石子游戏”,这个超简单的游戏有x颗石子的SG值显然就是x。其实,n堆石子的Nim游戏本身不就是n个“任取石子游戏”的和吗?

所以,对于我们来说,SG函数与“游戏的和”的概念不是让我们去组合、制造稀奇古怪的游戏,而是把遇到的看上去有些复杂的游戏试图分成若干个子游戏,对于每个比原游戏简

化很多的子游戏找出它的SG函数,然后全部异或起来就得到了原游戏的SG函数,就可以解决原游戏了。


感觉一大堆博弈题可以解决了。。。。。^_^


参考资料来自:

http://baike.baidu.com/view/2855458.htm

http://blog.sina.com.cn/s/blog_617615cd0100f6xv.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值