You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 1321. 取石子

一、题目描述

AliceBob 两个好朋友又开始玩取石子了。

游戏开始时,有 N 堆石子排成一排,然后他们轮流操作(Alice 先手),每次操作时从下面的规则中任选一个:

  • 从某堆石子中取走一个;
  • 合并任意两堆石子。

不能操作的人输。

Alice 想知道,她是否能有必胜策略。

输入格式 第一行输入 T,表示数据组数。

对于每组测试数据,第一行读入 N

接下来 N 个正整数 a_1,a_2,⋯,a_N ,表示每堆石子的数量。

输出格式 对于每组测试数据,输出一行。

输出 YES 表示 Alice 有必胜策略,输出 NO 表示 Alice 没有必胜策略。

数据范围 1≤T≤100,1≤N≤50,1≤a_i≤1000

输入样例:

3
3
1 1 2
2
3 4
3
2 3 5

输出样例:

YES
NO
NO

二、博弈论总结

必胜态 \Rightarrow 选择合适方案 \Rightarrow 对手必败态 必败态 \Rightarrow 选择任何路线 \Rightarrow 对手必胜态

对手聪明绝顶,不会犯错误,一旦他有机会获胜,他一定能找到合适的方案!所以,一定不能让他有机会,也就是总要让他总是处于必败状态,你才能获胜!

三、思考过程

Q1:本题中博弈的胜负与什么因素相关呢? :因为只有两种操作:拿走一个石子、合并两堆,很显然, 两个关键因素: 石子个数、堆数

Q2:一般情况是什么,特殊情况是什么呢? :如果某一堆石子只有1个,随着我们执行拿走1个的操作,它的堆就没了,这样石子个数变了,堆数也变了,一下变两个,问题变复杂了,上来就想难题,怕是搞不定。 既然这样,我们就思考一下 一般情况 :只考虑所有个数大于等于2的那些堆,其它可能存在石子数等于1的,等我们想明白这个一般情况问题再研究特殊情况的事,由易到难。

Q3:猜一下关联关系? 两个操作同一时间只能执行一个,可以理解为拿走一个石子对结果影响一下,合并两堆石子对结果也是影响一下,初步考虑应该堆个数与石子总数的加法关系相关。

一般情况:当每堆的石子个数都是大于等于2时,猜关联关系

设 剩余操作数 = b = 堆数 + 石子总数 - 1 结论:b是奇数⟺先手必胜,b是偶数⟺先手必败

我们可以发现,当n1的时候,也就是只有1堆时,比如a_0=3,那么b=3+1-1=3,是奇数:

  • ① 先手拿走一个,剩操作数=2
  • ② 后手只能拿走1个,剩操作数1
  • ③ 先手再拿走1个剩余操作数=0
  • ④ 后手没有可以拿的了,后手负,先手必胜!

结论显然成立。

当不只有1堆时,分类讨论:

情况1:没有数量为1的堆

先证明奇数必胜,对于先手来说,

  • n>1, 那么 只要选两堆合并 那么 总操作数变成偶数
  • n=1, 明显只能选择减少1操作,后手还是偶数。

对于后手来说,无论他是减少1,还是合并操作,留下的总操作数一定还是奇数。

对于某些读者来说,可能会问,如果后手把某个2变成1,先手该怎么办,其实这个很容易操作,如果堆数超过1,先手一定选择合并这个数量为1的堆,如果只有一堆了而且还是1,明显先手必胜了。 所以,先手总是有办法让后手必败(操作数为偶数的局面),后手无论怎么走,都会让先手必胜(变成操作为奇数的局面),所以我们证明成立。

在上面,我们也证明了当操作数是偶数的时候,先手是必败的。

情况2:有数量为1的堆

这个情况比较复杂,因为如果某个人把1减少1,那么这个堆同时也消失了,相当于操作数减少了2

  • 假设有a堆石子,其中每堆石子个数为1
  • 剩余堆的石子个数都严格大于1

根据这些数量大于1的堆的石子可以求出上述定义出的b,我们使用f(a, b)表示此时先手必胜还是必败,因为博弈论在本质上是可以递推的,我们可以想出起点,再想出递推关系,就可以递推得到更大数据情况下的递推值,也就是博弈论本质上是dp

Q:情况3为什么是两个表达式? 答: ①当右侧存在时,合并左边两堆石子,则右侧多出一堆石子,并且,石子个数增加2,也就是b+=3

②当右侧一个都没有的时候,左边送来了一堆,两个石子,按b的定义,是堆数+石子个数-1=2,即b+=2

六、实现代码

#include <bits/stdc++.h>
using namespace std;
const int N = 55, M = 50050; // M 包括了 50 * 1000 + 50个石子数量为1的堆数
int f[N][M];

int dfs(int a, int b) {
    int &v = f[a][b];
    if (~v) return v;
    // 简单情况: 即所有堆的石子个数都是严格大于1此时a是0
    if (!a) return v = b % 2; // 奇数为先手必胜,偶数为先手必败

    // 一般5个情况 + 1个特殊情况
    
    // 特殊情况: 如果操作后出现b中只有一堆且堆中石子个数为1
    // 那么应该归入到a中并且b为0
    // 以下所有情况,如果能进入必败态,先手则必胜!
    if (b == 1) return dfs(a + 1, 0);

    // 情况1有a从a中取一个
    if (a && !dfs(a - 1, b)) return v = 1;

    // 情况2, 4有b从b中取1个(石子总数 - 1) or 合并b中两堆(堆数 - 1),
    if (b && !dfs(a, b - 1)) return v = 1;

    // 情况3有a >= 2 合并a中两个
    // 如果b的堆数不为0 a - 2,  b + 1堆 + 2个石子只需要加delta  ====> b + 3
    // 如果b的堆数为0 a - 2,  0 + 2个石子 + 1堆 - 1  ====> b + 2
    if (a >= 2 && !dfs(a - 2, b + (b ? 3 : 2))) return v = 1;

    // 情况5有a有b 合并a中1个b中1个, a - 1, b的堆数无变化 + 1个石子只加delta
    if (a && b && !dfs(a - 1, b + 1)) return v = 1;

    // 其他情况,则先手处于必败状态
    return v = 0;
}

int main() {
    memset(f, -1, sizeof f);
    int T, n;
    cin >> T;
    while (T--) {
        cin >> n;
        int a = 0, b = 0;
        for (int i = 0; i < n; i++) {
            int x;
            cin >> x;
            if (x == 1) a++;
            // b != 0时 加1堆 + 加x石子 = 原来的 + x + 1 (其实就是区别一开始的时候)
            // 当b != 0时, 我们往后加的delta
            // b == 0时 加1堆 + 加x石子 = 0 + 1 + x - 1 = x
            // 注意操作符优先级
            else
                b += b ? x + 1 : x;
        }

        // 1 为先手必胜, 0为先手必败
        if (dfs(a, b))
            puts("YES");
        else
            puts("NO");
    }
    return 0;
}