【Nim游戏】POJ2975[Nim]题解

来源:互联网 发布:linux系统shell 编辑:程序博客网 时间:2024/05/17 08:16

题目概述

给出一个 Nim 游戏: n 堆石子,每堆石子 ai 个,求从初始状态到一个必胜态(对手必败)的方案数。

解题报告

太菜了……刚学 Nim 游戏……

Nim 游戏的简化模型就是 n 堆石子,每堆石子 ai 个,然后每次可以将一堆石子拿走若干个(不能不拿),游戏目的是拿光石子(对方无法操作)。

那么 Nim 游戏就有必胜态 W 和 必败态 L ,有以下性质:

  1. 无法操作的状态是 L
  2. 有一个子状态是 L 的状态是 W
  3. 没有一个子状态是 L 的状态是 L

那么怎么判断一个状态是 W 还是 L ?好像可以直接DP。但是 nai 一大就不行了。

可怕的地方来了……有一个定理:如果 ai 的异或和 Xor=0 ,那么就是 L ,否则就是 W 。怎么证明呢?其实只要三个性质能被体现出来就行了:

  1. 当无法操作时, Xor 显然为 0
  2. 当状态是 W 时, Xor>0 ,我们一定可以找到一个 ai ,满足 aiXor 的最高位上的数为 1 (二进制),这样 ai xor Xor<ai ,说明有一个子状态为 L
  3. 当状态是 L 时, Xor=0 ,则不可能找到2中的 ai ,所以没有一个子状态为 L

然而这道题更鬼畜一点,怎么求对手必败的方案数?我们来想,如果 Xor>0 ,那么我们就要想办法把 Xor 变成 0 ,所以我们枚举要拿的石子堆 i ,然后判断 Xor xor ai<ai 表示去掉 ai 的异或和, ai 要变成 Xor xor ai 才能使新 Xor 变为 0 ,但不能违反 Nim 游戏规则),如果成立,说明方案数 +1

示例程序

#include<cstdio>using namespace std;const int maxn=1000;int n,a[maxn+5],nim,ans;int main(){    freopen("program.in","r",stdin);    freopen("program.out","w",stdout);    for (scanf("%d",&n);n;scanf("%d",&n))    {        nim=0;for (int i=1;i<=n;i++) scanf("%d",&a[i]),nim^=a[i];        ans=0;for (int i=1;i<=n;i++) ans+=(nim^a[i])<a[i];printf("%d\n",ans);    }    return 0;}
原创粉丝点击