威佐夫博奕 Wythoff Game [转帖]

威佐夫博奕 Wythoff Game

有两堆石子,数量任意,可以不同.游戏开始由两个人轮流取石子.游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子.最后把石子全部取完者为胜者.现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者.

这是一道很经典的博弈题目,PKU1067,过这道题的人数很多,不过相信大多数都是直接搜到公式AC.我碰巧看到了此题的证明过程,就顺手来blog划拉两笔.

第一个是终极公式 An = [(1 + sqrt(5)) / 2 * n], Bn = [(3 + sqrt(5)) / 2 * n]; 代码是极其简短的…

#include <stdio.h>
#include <math.h>

int ak, bk;
double x;

int main() {
     x = (1 + sqrt(5.0)) / 2;
     while(scanf("%d %d", &ak, &bk) != EOF) {
          if(ak > bk) {
               ak ^= bk;
               bk ^= ak;
               ak ^= bk;
          }
          int k = bk - ak;
          if(ak == (int)(k * x)) printf("0\n");
          else printf("1\n");

     }
}

对必输态的结论我就直接使用了,不再重复证明,不明白可以自己证明,或看discuss~

必输态的结论:我们知道所有的必输的数对(a,b),假定a < b,具有两个性质,1.任何一个自然数在数对中都仅出现一次,任意一个b-a的差值也只出现一次,同时知道第n个点对的差值是n.

(0,0) (1,2) (3,5) (4,7) (6,10) (8,13)... 差值是0,1,2,3,4,5...

首先我们需要证明这么一个辅助定理:
对于任意两个正无理数r,t
如果 1 / r + 1 / t = 1
那么 An = [rn], Bn = [tn],可以不重复的覆盖所有自然数.([]为下取正的意思,x-1 < [x] < x)
我们来考察任意一个正数N,假设存在[rx] = N,并且存在[ty] = N,那么x = [N/r], y = [N/t].
(N/r-1)+(N/t-1) < [N/r]+[N/t] < N/r+N/t,
因为1/r+1/t=1, 所以得到N-2 < x+y < N,所以可知x+y只可能等于N-1 (这也可以说明An,Bn覆盖了N-1个小于N的数).
又因为[rx] = N, [ty] = N 所以N < (rx && ty) < N+1.
可知 N/x < r < (N+1)/x 和 N/y < t < (N+1)/t
继续 x/(N+1) < 1/r < x/N 和 y/(N+1) < 1/t < y/N
x/(N+1) + y/(N+1) < 1/r + 1/t < x/N + y/N
因为x + y = N - 1所以 (N-1)/(N+1) < 1/r + 1/t < (N-1)/N 和已知条件矛盾.
所以不存在一个数可同时使[rx] = N并且[ty] = N.
以上过程证明An,Bn不重复覆盖了所有自然数.

现在我们假设点对为 An = [rn], Bn = [tn], 1/r + 1/t = 1 并且 Bn - An = n
n = n + [rn] - [rn] = [n + rn] - [rn] = [(1+r)n] - [rn]
很容易知道如果(1+r) != t,则肯定存在一个足够大的n使[(1+r)n] 和 [tn] 相差1 那么则与[(1+r)n] - [rn] = n, [tn] - [rn] = n 矛盾
所以 1/r + 1/(r+1) = 1 解得 r = (1 + sqrt(5)) / 2, t = (3 + sqrt(5)) / 2;

当然,如果不知道辅助定理,以上证明根本不可能YY出来...下面隆重介绍一个极度YY的找规律方法,和另一种解法的证明.

按照正常的YY找规律做题方式,我们先人肉几组数据
1 (1,2) 1+2=3
2 (3,5) 3+5=8
3 (4,7) 4+7=11
4 (6,10) 6+10=16
5 (8,13) 8+13=21
6 (9,15) 9+15=24
7 (11,18) 11+18=29
8 (12,20) 12+20=32
9 (14,23) 14+23=37
10 (16,26) 16+26=42
11 (17,28) 17+28=45
12 (19,31) 19+31=50
13 (21,34) 21+34=55
14 (22,36) 22+36=58
15 (24,39) 24+39=63
16 (25,41) 25+41=66
17 (27,44) 27+44=71
18 (29,47) 29+47=76
先来说一组最明显的线索(1,2)(3,5)(8,13)(21,34)...居然有一个fib数列!再仔细观察发现(4,7)(11,18)(29,47)... (6,10)(16,26)...
以此我们将数对化为一个数对矩阵
(1,2)(3,5)(8,13)(21,34)...
(4,7)(11,18)(29,47)...
(6,10)(16,26)...
(9,15)...
第一条规律,我们可以发现任何一组都满足fib的特征,如果存在(a,b)项那么在第b项必然是(a+b,a+2b).
这条规律还不能让我们把全部序列构造出来,因为每一组都需要第一个数对(1,2),(4,7),(6,10),(9,15),(12,20)...我们姑且称这一列为"头数对",对应与头数对中的a,即1,4,6,9,12...我们称之为"头数".
假设将头数拿出来构成一个数列Sn = 1,4,6,9,12,14,17,19,22,25,27,30,33,35,38...这组数有什么特征?
4 = 3 + 1
6 = 5 + 1
9 = 8 + 1, 12 = 8 + 4
14 = 13 + 1, 17 = 13 + 4, 19 = 13 + 6
22 = 21 + 1, 25 = 21 + 4, 27 = 21 + 6, 30 = 21 + 9, 33 = 21 + 12
35 = 34 + 1, 38 = 34 + 4...
呵呵,看出点什么规律没?Sn由fib(n) + 一个小于fib(n-1)的Sn项来构成的.
知道了头数对的a,那么我们还需要知道数对位置k从而算出a+k=b
那么k是具有什么规律呢?我们考察所有头数对的位置得到1,3,4,6,8,9,11,12...这个数列有什么规律呢...将人肉的数据竖着看一下...是不是发现了它的存在?原来头数对的位置隐藏在每一项数对(a,b)的a里面...

但是这两条坑爹的规律仅仅可以用于构造,但并不能得到O(1)的计算方法,所以我们需要从中发掘出更坑爹的东西来...

大家知道计算机是二进制...下面介绍一种坑爹进制,叫斐波纳妾进制~这个进制每一位是 1 2 3 5 8 13 21... 这个进制是很坑爹的,因为不可能存在相邻的两个1,以及加减法也很坑爹,想要深入了解的同学,可以自行研究其坑爹之处...

那么我们将以上规律化成斐波纳妾进制...神奇的事情出现了!
(1,2)(3,5)(8,13)...变为了(1,10)(100,1000)(10000,100000)...
(4,7)(11,18)(29,47)...变为了(101,1010)(10100,101000)(1010000,10100000)
(6,10)(16,26)...变为了(1001,10010)(100100,1001000)
每一行写成正则式,变成了^10*,^1010*,^10010*,...
这规律太赤裸裸了...用这一条规律就可以O(1)秒杀题目了...即对于每一对(a,b),a以偶数个0结尾,b为a左移一位.
对于任意一个数x,我们判断它最后0的个数,如果是偶数个,则y=x左移一位,构成(x,y)数对.如果是奇数个,则y=x右移一位,构成(y,x)数对.

对于找规律解题到这里可以说已经OK了,但怎么证明正确呢,还看下面更神奇的规律及简要证明...
我们考察头数的构成的Sn数列,刚才对于Sn的规律对应到斐波纳妾进制中意味着什么呢?
1,4,6,9,12,14,17... 变成了 1,101,1001,10001,10101,100001,100101... 这些数恰恰是斐波纳妾进制0位为1的数.
我们从而得知,矩阵第一列数字都是为以1结束的斐波纳妾进制数(=.= 好长的名字... ),此后都是无限左移.这一点可以保证,每个自然数都有且仅被包含一次.
那么还有第二点需要证明,即没有两个数对,他们a,b的差值d是相同的.我们通过推导以下结论,来证明这一点.
即对任意差值d,都有唯一一个数对和其对应.
首先对于每一个数对(a,b)我们可知每一个b都是其后续的差值.我们仅仅需要证明每一个a对应一个不同的数对即可.(每一项a, 即1,100,10000,...,101,10100....)
但每一个b都和其后续对应,那么只剩下头数对没有对应的d.所以我们要证明的是每一个a都可以对应到一个头数对的差值.
这时候我们需要先仔细研究一下(a,b)和差值d的关系...很容易知道对于任何非头数对,其中b是a的左移,d是a的右移.
而对于1,101,1001,10001是没法右移的,但很容易得知,这时候d的计算方法是(a - 1)右移 + 1 = d.则a = (d - 1)左移 + 1
再往下出几个数字来证明,可推广至全部情形.

对于1,101,1001,很容易知道推导得对应的a也是头数,例如(101-1)<<1 + 1 = 1001,...
对于每一行我们假设头数是u,其对应的数v,u->v,即1->1, 101->1001, 1001->10001.

先科普一下在斐波纳妾进制中的减法… 10 – 1 = 1, 100 – 1 = 10, 1000 – 1 = 101, 10000 – 1 = 1010… 坑爹…
即 (1偶数个0) – 1 = 1010…10, (1奇数个0) – 1 = 101…01

而我们剩下的a恰好全部是最后带偶数0的…假设a为一个u前缀,带着偶数个0, u00..00.
那么 (u00..00 – 1)<<1 + 1 = (v0...10)<<1 + 1 = (v0..100) + 1 = v0..101,同样为尾部为1的数,满足对应到头数对中.
好了,坑爹的证明完毕,最后我们还得知了其中的对应关系, ^1(00)*->^1(01)*, ^101(00)*->^(1001)(01)*, ^1001(00)*->^(10001)(01)*…
半文字半正则描述一下, 前缀1(00)* -> 前缀01(01)*~ 至此全部完毕,附带一份简易代码.

#include <stdio.h>
typedef __int64 ll;
ll fib[100] = {1, 1};

int i2f(int x, int s[]) {
     int w = 1;
     while(fib[w] <= x) ++w;
     --w;
     for(int i = w; i > 0; --i) {
          if(x >= fib[i]) {
               s[i - 1] = 1;
               x -= fib[i];
          } else {
               s[i - 1] = 0;
          }
     }
     return w;
}

int f2i(int w, int s[]) {
     int ret = 0;
     for(int i = 0; i < w; ++i) if(s[i]) ret += fib[i + 1];
     return ret;
}

int solve(int x) {
     int w, s[100];
     w = i2f(x, s + 1);
     s[0] = 0;
     int n = 1;
     while(!s[n]) ++n;
     if(n & 1) {
          return f2i(w + 1, s);
     } else {
          return f2i(w - 1, s + 2);
     }
}

void print(int w, int s[]) {
     for(int i = w - 1; i >=0; --i) printf("%d",s[i]);
}

int main() {
     for(int i = 2; i < 100; ++i) {
          fib[i] = fib[i - 1] + fib[i - 2];
     }
     int a, b;
     while(scanf("%d %d", &a, &b) != EOF) {
          if(solve(a) == b) puts("0");
          else puts("1");
     }
     return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值