zoukankan      html  css  js  c++  java
  • 取石子游戏(博弈)

     
    看来半天没想到竟然是这么屌丝,浪费了那么长的时间,证明完全NB,就靠我现在的智商想完全的独立的想出来完全没有可能
     
     
     
     
    不过也“严重”说明了遇到这样困难的题目时,我们不应该轻言放弃。而应该仔细分析题目隐含的信息,学会分析和转化问题,从而找到问题的突破口,一举歼灭问题。
     
    下面是转载的一篇详细的解释。。。
     
    取石子游戏

    Description

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

    Input

    输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000,000。

    Output

    输出对应也有若干行,每行包含一个数字1或0,如果最后你是胜者,则为1,反之,则为0。

    Sample Input

    2 1 8 4 4 7

    Sample Output

    0 1 0

    Source

     

    转载分析:大致看完题目,想当然就知道这是一道博弈论的问题,最容易想的就是直接用博弈论的必败、必胜态进行动态规划求解。但是朴素的动态规划是 O(N * M) 的,如果做一些优化可能可以过掉 RQNOJ 的题目,但是对于 POJ 1067 来说就完全无能为力。所以我们尝试分析数据,看看有没有什么规律(以下用 (a, b) 表示两堆石子的个数,即游戏中的一个状态)。
    39
    40列举了几个状态之后容易发现,必胜态的数目比必败态要多很多,所以我们先手工求出前几个必败态:
    41
    42(1, 2)、(2, 1)、(3, 5)、(5, 3)、(4, 7)、(7, 4)、(6, 10)、(10, 6)……
    43
    44首先回顾必胜态和必败态的朴素求法:
    45
    46定理 0:一个状态是必败态,当且仅当它的所有后继状态都是必胜态;而一个状态是必胜态,只要它的后继状态有一个以上的必败态即可。
    47
    48证明略去。
    49
    50容易发现下面的定理:
    51
    52定理 1:(a,b) 和 (b, a) 的胜负性是相同的(a <> b)。
    53
    54证明:如果 (a, b) 是必胜态,那么将必胜策略中所有的操作,对第一堆的变为第二堆,对第二堆的变为第一堆,就构成 (b, a) 的必胜策略
    55
    56定理 2:若 (a, b) 是必败态,则对于所有的 x <> a 和 y <> b,(x, b) 和 (a, y) 是必胜态。
    57
    58证明:
    59
    60对于 x > a 和 y > b,不管是哪一种情况,总可以从 x 堆或 y 堆中取出一定量的石子使当前状态变为必败态 (a, b),由定理 1,(x, b) 和 (a, y) 为必胜态。
    61
    62对于 x < a 和 y < b,不管是哪一种情况,如果 (x, b) 或 (a, y) 是必败态的话,由上述可得 (a, b) 是必胜态,矛盾。故 (x, b) 和 (a, y) 均为为必胜态。
    63
    64定理 3: 若 (a, b) 是必败态,则对于所有的 d > 0,(a + d, b + d) 是必胜态。
    65
    66证明:
    67
    68与定理 2 类似。
    69
    70定理 4:在所有的必败态中,每个数字恰巧出现一次。
    71
    72证明:
    73
    74有了定理 1,对于对称的状态我们只需要处理其中一个,而两个数不会相同(相同的状态必然是必胜态),于是我们把每个状态中较小的数字放在前面,每行写一个状态,去掉括号并按照升序排列每行的第一个数,就构成了如下的矩阵:
    75
    761 2
    77
    783 5
    79
    804 7
    81
    826 10
    83
    84……
    85
    86观察这个矩阵,我们又可以得到新的定理:
    87
    88定理 5:矩阵中每行第一个数恰巧是前面每一行中没有出现过的最小正整数。
    89
    90证明:
    91
    92由定理 4,矩阵中每个数字恰巧出现一次,而按照这个矩阵的定义,第二列的数总比同行第一列大,第一列又按照升序排列,所以每一行的第一个数正好为前面每一行中没有出现过的最小正整数。
    93
    94定理 6:矩阵第 i 行的第二个数正好为第一个数加上 i
    95
    96证明:
    97
    98用数学归纳法。
    99
    1001) 对于第一行显然成立
    101
    1022) 若对于前 i - 1 行均成立,则所有的 (a[p], a[p] + p) (a[p] 为第 p 行第一个数,p < i) 均为必败态,那么考察第 i 行的状态 (a[i], a[i] + delta)。容易看出 delta >= i,因为如果 delta < i,一定可以通过一次操作变为前面出现过的必败态,那么这个状态就是必胜态。下面由 delta >= i,我们来说明 delta = i。
    103
    104首先,我们考虑从第一堆中取出 p 个石子,得到状态 (a[i] - p, a[i] + delta),由定理 5,比 a[i] 小的数都在之前出现过,若 a[i] - p 出现在某一行的第一列,由于存在必败态 (a[i] - p, a[i] - p + d) (d < delta),故 (a[i] - p, a[i] + delta) 一定为必胜态(定理 2);若 a[i] - p 出现在某一行的第二列,由于第一列是单增的,因而其对应的第一列数必小于 a[i] + delta,故而也可推出其状态为必胜态。
    105
    106对于从两堆石子中取出相同数目的情况与之类似,容易看出一定为必胜态。
    107
    108于是,(a[i], a[i] + delta) 状态的胜负性只与状态 (a[i], a[i] + d) (d < delta) 有关。不难看出,delta = i 时恰为必败态,因为不论从第二堆中取出多少个石子,作为另一堆的第一堆石子并没有在之前出现过,所以得到的一定是一个必胜态,因而 (a[i], a[i] + delta) 为必败态,由定理 2 及定理 4 可得,原命题成立。即矩阵中第 i 行第二列的数等于同行第一列的数加上 i。
    109
    110
    111这时,我们所有的问题都转化到了矩阵上,只要能通过合适的方法表示出这个矩阵,我们就可以很好地解决原问题。
    112
    113下面的过程可能需要比较高的数学技巧,首先给出我们需要的一个重要定理([x] 表示 x 的整数部分,{x} 表示 x 的小数部分,即 {x} = x - [x]):
    114
    115定理 7(Betty 定理):如果存在正无理数 A, B 满足 1/A + 1/B = 1,那么集合 P = { [At], t ∈ Z+}、Q = { [Bt], t ∈ Z+} 恰为集合 Z+ 的一个划分,即:P ∪ Q = Z+,P ∩ Q = ø。
    116
    117证明:暂时略去,将来补充。
    118
    119考虑到 Betty 定理中“恰为 Z+ 的划分”这一说,这意味着,Z+ 中的每个数都恰好出现一次,这与上述矩阵的性质十分吻合。于是我们猜想每一行第一列的数满足 [Φi] 的形式。
    120
    121于是我们得到每一行第二列的数为 [Φi] + i = [Φi + i] = [(Φ + 1)i]
    122
    123我们的目的是要让 Z+ 中每个数都在这个矩阵中出现,于是考虑到 Betty 定理的条件,Φ 和 (Φ + 1) 应满足 1/Φ + 1/(Φ + 1) = 1。解这个方程,我们得到 Φ = (sqrt(5) + 1) / 2,于是 Φ + 1 = (sqrt(5) + 3) / 2。
    124
    125Φ 恰为黄金分割比,这是多么令人惊奇的结论!
    126
    127于是应用 Betty 定理,我们得到最终我们需要的定理:
    128
    129定理 8:上述矩阵中每一行第一列的数为 [Φi],第二列的数为 [(Φ + 1)i],其中 Φ = (sqrt(5) + 1) / 2 为黄金分割比。
    130
    131证明:由 Betty 定理显然得证。
    132
    133
    134
    135有了定理 8,代码的实现就十分简单了,由于是数学算法,总复杂度为 O(1)。至此,本题完美解决。
    136
    137
    138总结:遇到这样困难的题目时,我们不应该轻言放弃。而应该仔细分析题目隐含的信息,学会分析和转化问题,从而找到问题的突破口,一举歼灭问题。

    代码

    #include <iostream>
    #include <cmath>    
    using namespace std;
    long a,b;
    int main()
    {
     while(cin>>a>>b)
     {
      long c;
      if(a>b)c=a,a=b,b=c;
      long x=(a+1.0)*2/(1.0+sqrt(5.0));
      long y=(a+0.0)*2/(1.0+sqrt(5.0));
      if(x==y)
    	  cout<<1<<endl;
      else if(x>y&&b==a+x)
    	  cout<<0<<endl;
      else 
    	  cout<<1<<endl;
     }
    }

  • 相关阅读:
    递归函数的原理
    html布局-子div浮动后,父容器撑不开解决
    权限设计——控制显示字段——设计思考
    winform上传文件到服务器——资料整理
    windows server自动化发布——技术积累与整理
    Docker——概念学习
    javascript中的var,let,const关键字
    Web前端面试图
    上传与预览图片
    每个程序员都应该知道延迟数—Latency Numbers Every Programmer Should Know
  • 原文地址:https://www.cnblogs.com/zswbky/p/5432054.html
Copyright © 2011-2022 走看看