Nim与反Nim

1. Nim游戏的定义

有若干堆硬币,两人轮流行动。每人可以选择其中一堆,取走若干枚硬币。最后一次取硬币的人获胜。

2. Nim问题的解

结论 :当且仅当N1 ^ N2 ^ N3 ^ …… ^ Nk = 0时,后手必胜
证明
(1) 当只有一堆硬币时,先手取走全部硬币,先手必胜。
(2) 当存在两堆硬币时,
a) 若N1 == N2,先手每次取多少,后手即在另一堆中取多少,因而后手必胜。
b) 若N1 != N2,不妨设a1 > a2,先手在a1中取走a1 – a2,那么剩下的就是两堆数量相等的硬币,因而先手必胜。
(3) 假设k-1、k-2堆硬币时,原命题成立(k >= 2)
考虑有k堆硬币时,
N1 = as * 2s + as-1 * 2s-1 +……+a0 * 20
N2 = bs * 2 s + bs-1 * 2 s-1 +……+b0 * 20
N3 = cs * 2s + cs-1 * 2s-1 +……+c0 * 20
……
Nk = ms * 2 s + ms-1 * 2 s-1 +……+m0 * 20
N1 ^ N2 ^ N3 ^ …… ^ Nk = 0 当且仅当 :
( as + bs + cs +……+ ms ) % 2 = 0
( as-1 + bs-1 + cs-1 +……+ ms-1 ) % 2 = 0
……
( a0 + b0 + c0 +……+ m0 ) % 2 = 0
设先手从第i堆拿走p枚硬币,设q = ai ^ (ai - p),
q = xt * 2t + xt-1 * 2t-1 + …… + x0 * 2 0 (t为q的二进制最高位)。
若xi = 1,则说明先手操作后,各堆石子在第i 位上的分解的和是奇数,即( as + bs+ cs +……+ ms ) % 2 = 1,各堆石子的异或和为q。
下面,后手的策略是消除先手行动后产生的“奇数”,使得各堆石子的异或和变成0。
必然存在第j堆,使得aj % 2t = 1, 那么我们选择在第j堆上操作,取m = aj – (aj ^ q),(由于aj >= 2t ,所以aj q < aj,即m>0),所以后手在第j堆中取m枚硬币即可。
K堆硬币会在这种平衡状态中减少,某一轮中有一堆或两堆硬币消失。(注意不可能取到只剩一堆的情况)
证毕。

3 反Nim问题

反Nim定义:不能行动的一方获胜
结论
(1) 如果全部为1,若为n为偶数,那么先手必胜
(2) 如果存在ai>1,那么如果异或和不为0,那么先手必胜
证明
(1) 显然
(2) 只有一个ai>1,必胜
(3) 存在两个或以上ai>1
a) Nim_sum == 0
可以转化成两种状态:
(a) 至少有两堆ai>1, Nim_sum!=0,转化为(3.b)
(b) 至少又一堆ai>1, Nim_sum!=0,转化成先手必胜态,则原态为先手必败态
b) Nim_sum !=0
可以转化为(3.a)
(3.a)先手必败,(3.b)先手必胜

4. Nim相关题目

4.1 CF 812 E

题意:给一棵树,每个节点有一些苹果,A,B两人轮流行动,每次行动为选择某一节点x的一些苹果,进行如下操作中的一个:
a) 如果x不是叶子节点,将该这些苹果移动到x的某一子节点上
b) 如果x为叶子节点,取走这些苹果
执行最后一次操作的人获胜。
本游戏中,B可以在游戏开始以前将树上两不同节点x, y的苹果树进行交换。
求有多少种交换方式,使得交换以后,B有必胜策略。
题解:如果某一节点距叶子节点的深度为奇数,给节点上的苹果最后必然被后手取走。如果为偶数,则看成Nim博弈。


#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=2e7+1;
const int M=3e5+10;
struct EDGE{
    int nxt,to;
}edge[M];
int h[N],t[M],len[M],a[M];
long long cnt,tmp;
int k;
void addedge(int x,int y)
{
    edge[++k].to=y;
    edge[k].nxt=t[x];
    t[x]=k;
}
void dfs(int x)
{
    int p,y;
    p=t[x];
    while(p)
    {
        y=edge[p].to;
        dfs(y);
        len[x]=len[y]^1;
        p=edge[p].nxt;
    }
    if(t[x]==0)
        len[x]=0;
    if(len[x]==1)
        h[a[x]]++;
    else
    {
        tmp^=a[x];
        cnt++;
    }
}
int main()
{
    int n,i,x;
    long long ans=0;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(i=2;i<=n;i++)
    {
        scanf("%d",&x);
        addedge(x,i);
    }
    dfs(1);
    for(i=1;i<=n;i++)
        if(len[i]==0)
            ans+=h[tmp^a[i]];
    if(tmp==0)
        ans+=cnt*(cnt-1)/2+(n-cnt)*(n-cnt-1)/2;
    printf("%lld",ans);
    return 0;
}

4.2 [ZJOI2009]取石子游戏

题意:n堆石子,A,B两人轮流行动。每次行动,参与人从最左或最右两堆种选一堆取走一部分石子,最后一个行动者获胜。
题解
设l[i][j]表示:如果[i,j]区间的左边为l[i][j],那么先手必败,后手必胜。
r[i][j]同理
l[i][j]存在且唯一:
唯一:如果存在p, q, p>q, p, q放在l[i][j]的左边皆可构成先手必败,那么p可取走(p-q)个成为q状态,那么先后手交换,新的后手(原先的先手)必败,矛盾。
存在:假设不存在,即无论左侧放多少,都不存在后手必败,那么假设先手从右侧取了一个,即达到后手必胜态,而l[i][j]又可取任意值,与唯一性矛盾(十分迷)。
求l[i][j](r[i][j])
(1) 初始化:l[i][i]=r[i][i]=a[i] (两堆石子,Nim)
(2) 取L=l[i][j-1],R=r[i][j-1],
a) R == x, l[i][j] =x
b) L<x&&R<x || L>x&&R>x, l[i][j] =x
c) L<=x&&R<x, l[i][j] =x+1
d) R<x&&x<L, l[i][j] =x-1

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1010;
int f[N][N],l[N][N],r[N][N],a[N];
int solve(int L,int R,int x)
{
    if(R==x)
        return 0;
    if((x<L&&x<R)||(x>L&&x>R))
        return x;
    if(L<=x&&x<R)
        return x+1;
    if(R<x&&x<L)
        return x-1;
}
int main()
{
    int T,n,i,j,k;
    scanf("%d",&T);
    while(T)
    {
        T--;
        scanf("%d",&n);
        for(i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(i=1;i<=n;i++)
        {
            l[i][i]=a[i];
            r[i][i]=a[i];
        }
        for(k=2;k<=n-1;k++)
            for(i=1;i<=n-k+1;i++)
            {
                j=i+k-1;
                l[i][j]=solve(l[i][j-1],r[i][j-1],a[j]);
                r[i][j]=solve(r[i+1][j],l[i+1][j],a[i]);
            }
        printf("%d\n",a[1]==l[2][n]?0:1);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值