博弈论

nim游戏


Nim游戏是博弈论中最经典的模型(之一),它又有着十分简单的规则和无比优美的结论 Nim游戏是组合游戏(Combinatorial Games)的一种,准确来说,属于“Impartial Combinatorial Games”(以下简称ICG)。

例题:

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

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

输入格式

第一行包含整数 n。

第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量。

输出格式

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

否则,输出 No

数据范围

1≤n≤10^5^
1≤每堆石子数≤10^9^

输入样例:

2
2 3

输出样例:

Yes
import java.util.Scanner;

public class Main {
    static int N = (int)1e5 + 10;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int res = 0;

        while (n -- > 0){
            int x = sc.nextInt();
            res ^= x;
        }

        if (res == 1) System.out.println("Yes");
        else System.out.println("No");

        sc.close();
    }
}

台阶-Nim游戏

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

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

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

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

输入格式

第一行包含整数 n。

第二行包含 n 个整数,其中第 i 个整数表示第 i 级台阶上的石子数 ai。

输出格式

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

否则,输出 No

数据范围

1≤n≤10^5^
1≤ai≤10^9^

输入样例:

3
2 1 3

输出样例:

Yes
import java.util.Scanner;

public class Main {
    static int n, res;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for (int i = 1; i <= n; i++) {
            int v = sc.nextInt();

            if ((i & 1) == 1) res ^= v;
        }

        if (res == 1) System.out.println("Yes");
        else System.out.println("No");

        sc.close();
    }
}

集合-Nim游戏

给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。

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

输入格式

第一行包含整数 k,表示数字集合 SS 中数字的个数。

第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i 个数 si。

第三行包含整数 n。

第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 hi。

输出格式

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

否则,输出 No

数据范围

1≤n,k≤100
1≤si,hi≤10000

输入样例:

2
2 5
3
2 4 7

输出样例:

Yes

Mex运算和SG函数

import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;

public class Main {
    static int M = 110;
    static int N = (int)1e4 + 10;
    static int k;
    static int s[] = new int[M];//存放集合中的元素
    static int sg[] = new int[N];//存放某个数的SG函数的值

    public static void main(String[] args) {
        Arrays.fill(sg, - 1);
        Scanner sc = new Scanner(System.in);
        k = sc.nextInt();

        for (int i = 0; i < k; i++) {
            s[i] = sc.nextInt();
        }

        int n = sc.nextInt();
        int res = 0;

        while (n -- > 0){
            int x = sc.nextInt();
            res ^= sg(x);//直接异或sg函数的和 进行判断
        }

        System.out.println(res != 0 ? "Yes" : "No");

        sc.close();
    }

    private static int sg(int x){//递归
        if (sg[x] != -1) return sg[x];//若sg存在直接返回
        
        HashSet set = new HashSet<>();//每次递归重新创建一个set集合

        for (int i = 0; i < k; i++) {//枚举集合中每一个数 进行比较
            if (x >= s[i]) set.add(sg(x - s[i]));//递归往set集合中添加sg函数
        }

        for (int i = 0; i <= N; i++) {//在集合中找,最小的不存在的值
            if (!set.contains(i)) return sg[x] = i;//找到,保存,返回
        }

        return 0;
    }

}