# 挑战程序竞赛系列（52）：4.2 Nim 与 Grundy 数

## POJ 2975: Nim

P312给了一个思路，对每个堆的石子数量进行异或，如果异或值非等于零，则先手必胜，否则后手必胜。

{1, 3, 2}

{1, {1, 2}, {2}}

(xi - m) 异或 (XOR 异或 xi) = 0

m = xi - xi ^ xor;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class Main{

String INPUT = "./data/judge/201709/P2975.txt";

public static void main(String[] args) throws IOException {
new Main().run();
}

void solve() {
while (true) {
int n = ni();
if (n == 0) break;
int[] piles = new int[n];
for (int i = 0; i < n; ++i) piles[i] = ni();
int x = 0;
for (int i = 0; i < n; ++i) {
x ^= piles[i];
}

if (x == 0) out.println("0");
else {
int ans = 0;
for (int i = 0; i < n; ++i) {
if (piles[i] > (x ^ piles[i])) ans++;
}
out.println(ans);
}
}
}

FastScanner in;
PrintWriter out;

void run() throws IOException {
boolean oj;
try {
oj = ! System.getProperty("user.dir").equals("F:\\java_workspace\\leetcode");
} catch (Exception e) {
oj = System.getProperty("ONLINE_JUDGE") != null;
}

InputStream is = oj ? System.in : new FileInputStream(new File(INPUT));
in = new FastScanner(is);
out = new PrintWriter(System.out);
long s = System.currentTimeMillis();
solve();
out.flush();
if (!oj){
System.out.println("[" + (System.currentTimeMillis() - s) + "ms]");
}
}

public boolean more(){
return in.hasNext();
}

public int ni(){
return in.nextInt();
}

public long nl(){
return in.nextLong();
}

public double nd(){
return in.nextDouble();
}

public String ns(){
return in.nextString();
}

public char nc(){
return in.nextChar();
}

class FastScanner {
StringTokenizer st;
boolean hasNext;

public FastScanner(InputStream is) throws IOException {
hasNext = true;
}

public String nextToken() {
while (st == null || !st.hasMoreTokens()) {
try {
} catch (Exception e) {
hasNext = false;
return "##";
}
}
return st.nextToken();
}

String next = null;
public boolean hasNext(){
next = nextToken();
return hasNext;
}

public int nextInt() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Integer.parseInt(more);
}

public long nextLong() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Long.parseLong(more);
}

public double nextDouble() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Double.parseDouble(more);
}

public String nextString(){
if (next == null){
hasNext();
}
String more = next;
next = null;
return more;
}

public char nextChar(){
if (next == null){
hasNext();
}
String more = next;
next = null;
return more.charAt(0);
}
}
}


## POJ 3537: Crosses and Crosses

1 2 3 4 5 6 7 8
x

4 的周围是绝对不能打的，因为这样先手就必胜了，于是划分为两个子问题：
{1}, {7,8}

grundy数告诉我们，集合{1}和{7,8}如果【本质】上是一样的话，就可以采取对称策略，那么后手必输。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main{

String INPUT = "./data/judge/201709/P3537.txt";

public static void main(String[] args) throws IOException {
new Main().run();
}

static final int MAX = 2000 + 16;
int[] mem;

void solve() {
mem = new int[MAX];
Arrays.fill(mem, -1);
while (more()) {
int n = ni();
out.println(grundy(n) != 0 ? "1" : "2");
}
}

int grundy(int n) {
if (n < 0) return 0;
if (mem[n] != -1) return mem[n];
int[] count = new int[MAX];
for (int i = 1; i <= n; ++i) {
count[grundy(i - 3) ^ grundy(n - i - 2)] = 1;
}

int res = 0;
while (count[res] != 0) res ++;
return mem[n] = res;
}

FastScanner in;
PrintWriter out;

void run() throws IOException {
boolean oj;
try {
oj = ! System.getProperty("user.dir").equals("F:\\java_workspace\\leetcode");
} catch (Exception e) {
oj = System.getProperty("ONLINE_JUDGE") != null;
}

InputStream is = oj ? System.in : new FileInputStream(new File(INPUT));
in = new FastScanner(is);
out = new PrintWriter(System.out);
long s = System.currentTimeMillis();
solve();
out.flush();
if (!oj){
System.out.println("[" + (System.currentTimeMillis() - s) + "ms]");
}
}

public boolean more(){
return in.hasNext();
}

public int ni(){
return in.nextInt();
}

public long nl(){
return in.nextLong();
}

public double nd(){
return in.nextDouble();
}

public String ns(){
return in.nextString();
}

public char nc(){
return in.nextChar();
}

class FastScanner {
StringTokenizer st;
boolean hasNext;

public FastScanner(InputStream is) throws IOException {
hasNext = true;
}

public String nextToken() {
while (st == null || !st.hasMoreTokens()) {
try {
} catch (Exception e) {
hasNext = false;
return "##";
}
}
return st.nextToken();
}

String next = null;
public boolean hasNext(){
next = nextToken();
return hasNext;
}

public int nextInt() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Integer.parseInt(more);
}

public long nextLong() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Long.parseLong(more);
}

public double nextDouble() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Double.parseDouble(more);
}

public String nextString(){
if (next == null){
hasNext();
}
String more = next;
next = null;
return more;
}

public char nextChar(){
if (next == null){
hasNext();
}
String more = next;
next = null;
return more.charAt(0);
}
}
}


## Codeforces 138D: World of Darkraft

public class Diagonal {

public static void main(String[] args) {
int n = 8;
int w = 10;
String[][] board = new String[n][w];

int cnt = 1;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < w; ++j) {
board[i][j] = cnt <= 9 ? "0" + cnt : cnt + "";
cnt ++;
}
}

String[][] trans = new String[n + w][n + w];
for (int i = 0; i < n + w; ++i) {
for (int j = 0; j < n + w; ++j) {
trans[i][j] = "  ";
}
}

for (int i = 0; i < n; ++i) {
for (int j = 0; j < w; ++j) {
if (((i + j) & 1) == 0) {
int ni = i + j;
int nj = j - i + n;
trans[ni][nj] = board[i][j];
}
}
}

pp(board);
pp(trans);
}

public static void pp(String[][] board) {
StringBuilder sb = new StringBuilder();
int n = board.length;
int m = board[0].length;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
sb.append(board[i][j] + ((j + 1 == m) ? "\n" : " "));
}
}
System.out.println(sb.toString());
}
}


01 02 03 04 05 06 07 08 09 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80

01

21    12    03

41    32    23    14    05

61    52    43    34    25    16    07

72    63    54    45    36    27    18    09

74    65    56    47    38    29    20

76    67    58    49    40

78    69    60

80



import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;

public class Main{

String INPUT = "./data/judge/201709/C138D.txt";

public static void main(String[] args) throws IOException {
new Main().run();
}

int n, m;
char[][] board;
void solve() {
n = ni();
m = ni();

board = new char[n][m];
for (int i = 0; i < n; ++i) {
board[i] = ns().toCharArray();
}

out.println((calc(0) ^ calc(1)) != 0 ? "WIN" : "LOSE");
}

int[][][][] dp;
private int calc(int mod) {
int N = n + m;
dp = new int[N + 2][N + 2][N + 2][N + 2];
ArrayUtils.fill(dp, -1);
return grundy(0, 0, N, N, mod);
}

int grundy(int row_min, int col_min, int row_max, int col_max, int mod) {
if (dp[row_min][col_min][row_max][col_max] != -1) return dp[row_min][col_min][row_max][col_max];

Set<Integer> set = new HashSet<Integer>();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (((i + j) & 1) == mod) {
int ni = i + j;
int nj = j - i + n;
if (inside(ni, nj, row_min, col_min, row_max, col_max)) {
if (board[i][j] == 'L') {
int g1 = grundy(row_min, col_min, ni,      col_max, mod);
int g2 = grundy(ni + 1,  col_min, row_max, col_max, mod);
}

if (board[i][j] == 'R') {
int g1 = grundy(row_min, col_min, row_max, nj,      mod);
int g2 = grundy(row_min, nj + 1,  row_max, col_max, mod);
}

if (board[i][j] == 'X') {
int g1 = grundy(row_min, col_min, ni,      nj,      mod);
int g2 = grundy(row_min, nj + 1,  ni,      col_max, mod);
int g3 = grundy(ni + 1,  col_min, row_max, nj,      mod);
int g4 = grundy(ni + 1,  nj + 1,  row_max, col_max, mod);
set.add(g1 ^ g2 ^ g3 ^ g4);
}
}
}
}
}

int res = 0;
while (set.contains(res)) res ++;
return dp[row_min][col_min][row_max][col_max] = res;
}

boolean inside(int x, int y, int row_min, int col_min, int row_max, int col_max) {
return x >= row_min && x < row_max && y >= col_min && y < col_max;
}

FastScanner in;
PrintWriter out;

void run() throws IOException {
boolean oj;
try {
oj = ! System.getProperty("user.dir").equals("F:\\java_workspace\\leetcode");
} catch (Exception e) {
oj = System.getProperty("ONLINE_JUDGE") != null;
}

InputStream is = oj ? System.in : new FileInputStream(new File(INPUT));
in = new FastScanner(is);
out = new PrintWriter(System.out);
long s = System.currentTimeMillis();
solve();
out.flush();
if (!oj){
System.out.println("[" + (System.currentTimeMillis() - s) + "ms]");
}
}

public boolean more(){
return in.hasNext();
}

public int ni(){
return in.nextInt();
}

public long nl(){
return in.nextLong();
}

public double nd(){
return in.nextDouble();
}

public String ns(){
return in.nextString();
}

public char nc(){
return in.nextChar();
}

class FastScanner {
StringTokenizer st;
boolean hasNext;

public FastScanner(InputStream is) throws IOException {
hasNext = true;
}

public String nextToken() {
while (st == null || !st.hasMoreTokens()) {
try {
} catch (Exception e) {
hasNext = false;
return "##";
}
}
return st.nextToken();
}

String next = null;
public boolean hasNext(){
next = nextToken();
return hasNext;
}

public int nextInt() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Integer.parseInt(more);
}

public long nextLong() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Long.parseLong(more);
}

public double nextDouble() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Double.parseDouble(more);
}

public String nextString(){
if (next == null){
hasNext();
}
String more = next;
next = null;
return more;
}

public char nextChar(){
if (next == null){
hasNext();
}
String more = next;
next = null;
return more.charAt(0);
}
}

static class ArrayUtils {

public static void fill(int[][] f, int value) {
for (int i = 0; i < f.length; ++i) {
Arrays.fill(f[i], value);
}
}

public static void fill(int[][][] f, int value) {
for (int i = 0; i < f.length; ++i) {
fill(f[i], value);
}
}

public static void fill(int[][][][] f, int value) {
for (int i = 0; i < f.length; ++i) {
fill(f[i], value);
}
}
}
}



## POJ 2315: Football Game

http://www.hankcs.com/program/algorithm/poj-2315-football-game.html

XOR又称半加运算，即只执行加法而不执行进位。在原始Nim游戏中，只允许选取1堆，所以最终XOR的结果是以2为进制执行半加运算。在此处，每次可选M座石头堆，进制则为M+1。在实现的时候可以先不管进位，只做加法，最终对M+1求模，将carries去掉。

{1},{2},{3} m = 2

0001
0010
0011
---------
0022

{1},{1},{1}, m = 2

0001
0001
0001
--------
0000

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main{

String INPUT = "./data/judge/201709/P2315.txt";

public static void main(String[] args) throws IOException {
new Main().run();
}

static final int    MAX_N = 30;
static final int    MAX_S = 27;
static final double PI    = 3.14159265358979323846264338327950288;

int N, M, L, R;
int[] S;
int[] XOR;

void solve() {
while (more()) {
N = ni();
M = ni();
L = ni();
R = ni();
S   = new int[MAX_N];
XOR = new int[MAX_S];
for (int i = 0; i < N; ++i) {
S[i] = ni();
}

out.println(nim() ? "Alice" : "Bob");
}
}

boolean nim() {

int K = distance(L);  // 可以选取 K - 1 个石头

for (int i = 0; i < N; ++i) {
int g = distance(S[i]) % K;
for (int j = 0; g != 0; ++j, g >>= 1) {
XOR[j] += g & 1;
}
}

for (int i = 0; i < MAX_S; ++i) {
if (XOR[i] % (M + 1) != 0) return true;
}
return false;
}

int distance(int x) {
return (int) (x / (2 * PI * R)) + 1;
}

FastScanner in;
PrintWriter out;

void run() throws IOException {
boolean oj;
try {
oj = ! System.getProperty("user.dir").equals("F:\\java_workspace\\leetcode");
} catch (Exception e) {
oj = System.getProperty("ONLINE_JUDGE") != null;
}

InputStream is = oj ? System.in : new FileInputStream(new File(INPUT));
in = new FastScanner(is);
out = new PrintWriter(System.out);
long s = System.currentTimeMillis();
solve();
out.flush();
if (!oj){
System.out.println("[" + (System.currentTimeMillis() - s) + "ms]");
}
}

public boolean more(){
return in.hasNext();
}

public int ni(){
return in.nextInt();
}

public long nl(){
return in.nextLong();
}

public double nd(){
return in.nextDouble();
}

public String ns(){
return in.nextString();
}

public char nc(){
return in.nextChar();
}

class FastScanner {
StringTokenizer st;
boolean hasNext;

public FastScanner(InputStream is) throws IOException {
hasNext = true;
}

public String nextToken() {
while (st == null || !st.hasMoreTokens()) {
try {
} catch (Exception e) {
hasNext = false;
return "##";
}
}
return st.nextToken();
}

String next = null;
public boolean hasNext(){
next = nextToken();
return hasNext;
}

public int nextInt() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Integer.parseInt(more);
}

public long nextLong() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Long.parseLong(more);
}

public double nextDouble() {
if (next == null){
hasNext();
}
String more = next;
next = null;
return Double.parseDouble(more);
}

public String nextString(){
if (next == null){
hasNext();
}
String more = next;
next = null;
return more;
}

public char nextChar(){
if (next == null){
hasNext();
}
String more = next;
next = null;
return more.charAt(0);
}
}

static class ArrayUtils {

public static void fill(int[][] f, int value) {
for (int i = 0; i < f.length; ++i) {
Arrays.fill(f[i], value);
}
}

public static void fill(int[][][] f, int value) {
for (int i = 0; i < f.length; ++i) {
fill(f[i], value);
}
}

public static void fill(int[][][][] f, int value) {
for (int i = 0; i < f.length; ++i) {
fill(f[i], value);
}
}
}
}