zoukankan      html  css  js  c++  java
  • 博弈论

     

     Nim游戏:

      给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

    问如果两人都采用最优策略.

    /*
    先手必胜状态:可以走到某一个必败状态(2,2)(0,0)
    先手必败状态:走不到任何一个必败状态(1,2)(0,2)
    */

     Nim游戏:两人都采取最优策略,谁先遇到相等的状态(0,0)就必输,比如(2,3)先手可以通过制造成(2,2)这样的状态,然后后手在一堆中拿多少,先手就也从另外一堆中拿一样的个数镜像操作,使得后者一定会先遇到(0,0)这样的局面。

    必胜状态:用某种方式拿完之后可以让剩下的状态成为先手必败状态。

    必败状态:走到所有的状态都是对手先手必胜的状态都是必败状态

    如果有N堆石子,a1, a2, a3, ……,an,那么a1 ^ a2 ^ a3 ^ …… ……an = 0,为0的是先手必败状态,不为0的为先手必胜状态。

    1)当没有石子时,即已经到达了终点时没有任何操作,0 ^ 0 ^ 0 ^ …… ^ 0 = 0;

    2)a1 ^ a2 ^ a3 ^ …… ^ an != 0,通过某种操作,从某一堆石子中拿走若干石子,使剩下的异或为0.

    证明过程:a1 ^ a2 ^ a3 ^ …… ^ an = x, 那么与x的最高位1相同的肯定有不少于1位ai(因为假如所有的a在x的最高位没有1个是1的话,那么异或的结果x中这一位肯定是0 ,矛盾),从ai堆我们取走 ai - (ai^x)(ai^x, x最高的左边因为都是0,所以原来ai在这些位中0还是0,1还是1,不会变,但是x是1的最高位这里由1变成了0,变小了,后面的无论怎么变都是小的,所以ai ^ x < ai。那么第ai堆剩下的石子就是 ai = ai - (ai - ai ^ x) = ai ^ x;带入原来的式子

    a1 ^ a2 ^ a3 ^ …… ^ ai ^ x ^ ai+1 ^ …… ^ an  = x ^ x = 0;

    所以如果不等于的时候我必然可以通过取走一些石子使之变成0.

    3)a1 ^ a2 ^ a3 ^ …… ^ an  = 0, 我能不能通过取走某堆的一些石子使之还是变成0呢?假设ai堆取走之后的剩余石子为aj 那么a1 ^ a2 ^ a3 ^ …… aj ^……an = 0, ai ^ aj = 0, 那么ai = aj.但是我们一定要取石子的,不能不取,所以原来异或是0的局面无法造成异或还是0的局面。矛盾了。

    所以异或为0为先手必败局面,异或不为0为先手必胜局面。

    那么为什么异或为0是输的呢?因为最终谁先面临全为0的状态是输的,题意中无法操作者视为失败,所以全是0者肯定是输的一方,然后我非0的一方肯定可以使之转化为全0的局面,然后全0的一方也只能转化为非0的局面,这样因为石子是不断被两方取走的,减少的,所以各堆的石子局面一定会变成全是0的状态,而全是0的状态异或为0 必然是上一次异或非0的局面转化而来的。所以综上,异或为0的肯定是先手必输局面。

    PS:等式两边异或相同的数字依然是相等的,然后一个数异或另外一个数等于0,那么这另外一个数肯定等于自己。

    Nim台阶游戏:

    现在,有一个nn级台阶的楼梯,每级台阶上都有若干个石子,其中第ii级台阶上有aiai个石子(i1i≥1)。

    两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。

    已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。

    问如果两人都采用最优策略,先手是否必胜。

    输入格式

    第一行包含整数nn。

    第二行包含nn个整数,其中第ii个整数表示第ii级台阶上的石子数aiai。

    输出格式

    如果先手方必胜,则输出“Yes”。

    否则,输出“No”。

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int n;
        cin>>n;
        int res = 0;
        for(int i = 1;i<=n;i++)
        {
            int a;
            cin>>a;
            if(i % 2) res ^= a;
        }
        if(!res) cout<<"No"<<endl;
        else cout<<"Yes"<<endl;
    }

    所有奇数台阶的异或为0时必败,非0时必胜。

    如果一开始异或为0,对手从偶数拿下来,那么就把这个拿下来的继续顺拿到下个台阶。奇数异或和为0保持不变。如果从奇数拿下来,同上面经典NIM游戏一样,只能变为异或非0的状态,我一定可以使之再变为为0的状态。

    所以我最终一定可以使之变为全部为0 异或也为0的最终目标态的上一步,然后使之变为全0,对手输的局面。

    疑点:1、这里即使奇数全为0,偶数台阶还有怎么办?

    偶数台阶对手拿下多少?我就从拿到奇数的多少继续拿下去,保持奇数台阶全为0不变,然后这样镜像操作一定可以台阶全为0的状态。

    2、为什么不可以按照偶数异或和为0?

    因为如果对手处于偶数异或和为0状态,这时候他把第一个台阶的全部拿到了地面之后,那么我们就只能破坏这个偶数异或为0的状态,根据经典Nim游戏,谁破坏了异或为0的的这个局面,就是必败局面。

    集合-NIM游戏

    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <unordered_set>
    
    using namespace std;
    
    const int N = 110, M = 10010;
    
    int n, m;
    int s[N], f[M];
    
    int sg(int x)
    {
        if(f[x] != -1) return f[x];
        
        unordered_set<int> S;
        for(int i = 0;i < m; i++)
        {
            int sum = s[i];
            if(x >= sum) S.insert(sg(x - sum));
        }
        
        for(int i = 0; ; i ++)
        {
            if(!S.count(i))
                return f[x] = i;
        }
    }
    
    int main()
    {
        cin>>m;
        for(int i = 0 ;i < m; i++) cin>>s[i];
        cin>>n;
        
        memset(f, -1 ,sizeof f);
        
        int res = 0;
        for(int i = 0; i < n; i++)
        {
            int x;
            cin>>x;
            res ^= sg(x);
        }
        if(res) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }

     所有起点状态的SG(x)异或起来,如果是0,必败,非0,必胜。

    输入:

    2
    2 5
    1
    10

    输出:

    sg[i]:-2 f[i]:0
    sg[i]:-5 f[i]:0
    sg[i]:0 f[i]:0
    sg[i]:-3 f[i]:0
    sg[i]:2 f[i]:1
    sg[i]:-1 f[i]:0
    sg[i]:4 f[i]:0
    sg[i]:-1 f[i]:0
    sg[i]:-4 f[i]:0
    sg[i]:1 f[i]:0
    sg[i]:6 f[i]:1
    sg[i]:1 f[i]:0
    sg[i]:-2 f[i]:0
    sg[i]:3 f[i]:1
    sg[i]:8 f[i]:0
    sg[i]:3 f[i]:1
    sg[i]:0 f[i]:0
    sg[i]:5 f[i]:2
    Yes

     疑问:如何通过这两句就达到我们给集合sg元素赋上不属于它后面能够到达的最小自然数的?主要是这递归怎么理解?

    拆分Nim游戏:

    给定nn堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。

    问如果两人都采用最优策略,先手是否必胜。

    输入格式

    第一行包含整数nn。

    第二行包含nn个整数,其中第ii个整数表示第ii堆石子的数量aiai。

    输出格式

    如果先手方必胜,则输出“Yes”。

    否则,输出“No”。

    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #include <unordered_set>
    using namespace std;
    const int N = 110;
    int f[N];
    int sg(int x)
    {
        if(f[x] != -1) return f[x];//记忆化搜索,每个局面sg的值
        unordered_set<int> S;
        //能够到达的两种局面
        for(int i = 0; i < x; i ++)
            for(int j = 0; j <= i; j++)
            {
                S.insert(sg(i) ^ sg(j));
            }
        //mex操作
        for(int i = 0; ; i++)
            if(!S.count(i)) return f[x] = i;
    }
    int main()
    {
        int n;
        cin>>n;
        memset(f,-1,sizeof f);
        int res = 0;
        for(int i = 0; i < n; i++)
        {
            int x;
            cin>>x;
            res ^= sg(x);
        }
        if(res) puts("Yes");
        else puts("No");
    }
  • 相关阅读:
    Python基本数据统计(二)---- 数据选择 & 简单统计与处理
    python2.7无法使用上下左右和backspace
    Python基本数据统计(一)---- 便捷数据获取 & 数据准备和整理 & 数据显示
    CentOS 6.5下安装NumPy、SciPy、Scikit-Learn
    hihocoder 1296 数论三·约瑟夫问题
    The 13th Zhejiang Provincial Collegiate Programming Contest
    hihocoder1181 欧拉路
    UPCOJ2985 Gopher(二分匹配)
    HNU13377 Book Club(二分匹配)
    HDU4799 LIKE vs CANDLE(树形dp)
  • 原文地址:https://www.cnblogs.com/longxue1991/p/12736892.html
Copyright © 2011-2022 走看看