博弈论

 

 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");
}
原文地址:https://www.cnblogs.com/longxue1991/p/12736892.html