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.

161 lines
6.4 KiB

2 years ago
##[$AcWing$ $1321$. 取石子](https://www.acwing.com/problem/content/description/1323/)
### 一、题目描述
$Alice$ 和 $Bob$ 两个好朋友又开始玩取石子了。
游戏开始时,有 $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$
**输入样例:**
```cpp {.line-numbers}
3
3
1 1 2
2
3 4
3
2 3 5
```
**输出样例:**
```cpp {.line-numbers}
YES
NO
NO
```
### 二、博弈论总结
<font size=4 color='red'><b>
2 years ago
必胜态 $\Rightarrow$ 选择合适方案 $\Rightarrow$ 对手必败态
必败态 $\Rightarrow$ 选择任何路线 $\Rightarrow$ 对手必胜态
</b></font>
2 years ago
2 years ago
对手聪明绝顶,不会犯错误,一旦他有机会获胜,他一定能找到合适的方案!所以,一定不能让他有机会,也就是总要让他总是处于必败状态,你才能获胜!
2 years ago
2 years ago
### 三、思考过程
2 years ago
2 years ago
**$Q1$:本题中博弈的胜负与什么因素相关呢?**
**答**:因为只有两种操作:**拿走一个石子、合并两堆**,很显然, 两个关键因素: **石子个数、堆数**
2 years ago
2 years ago
**$Q2$:一般情况是什么,特殊情况是什么呢?**
**答**:如果某一堆石子只有$1$个,随着我们执行拿走$1$个的操作,它的堆就没了,这样石子个数变了,堆数也变了,一下变两个,问题变复杂了,上来就想难题,怕是搞不定。
既然这样,我们就思考一下 **一般情况** :只考虑所有个数大于等于$2$的那些堆,其它可能存在石子数等于$1$的,等我们想明白这个一般情况问题再研究特殊情况的事,由易到难。
2 years ago
2 years ago
**$Q3$:猜一下关联关系?**
两个操作同一时间只能执行一个,可以理解为拿走一个石子对结果影响一下,合并两堆石子对结果也是影响一下,初步考虑应该堆个数与石子总数的加法关系相关。
2 years ago
2 years ago
**一般情况:当每堆的石子个数都是大于等于$2$时,猜关联关系**
2 years ago
2 years ago
<font size=5 color='red'><center><b>设 剩余操作数 = $b$ = 堆数 + 石子总数 - $1$</b></center></font>
2 years ago
<font size=5 color='red'><center><b>结论:$b$是奇数⟺先手必胜,$b$是偶数⟺先手必败</b></center></font>
2 years ago
2 years ago
我们可以发现,当$n$是$1$的时候,也就是只有$1$堆时,比如$a_0=3$,那么$b=3+1-1=3$,是奇数:
- ① 先手拿走一个,剩操作数=$2$
- ② 后手只能拿走$1$个,剩操作数$1$
- ③ 先手再拿走1个剩余操作数$=0$
- ④ 后手没有可以拿的了,后手负,先手必胜!
2 years ago
2 years ago
结论显然成立。
当不只有$1$堆时,分类讨论:
2 years ago
**情况$1$:没有数量为$1$的堆**
先证明奇数必胜,对于先手来说,
- $n>1$, 那么 **只要选两堆合并** 那么 **总操作数变成偶数**
- $n=1$, 明显只能选择减少$1$操作,后手还是偶数。
对于后手来说,无论他是减少$1$,还是合并操作,留下的总操作数一定还是奇数。
对于某些读者来说,可能会问,如果后手把某个$2$变成$1$,先手该怎么办,其实这个很容易操作,如果堆数超过$1$,先手一定选择合并这个数量为$1$的堆,如果只有一堆了而且还是$1$,明显先手必胜了。 所以,先手总是有办法让后手必败(操作数为偶数的局面),后手无论怎么走,都会让先手必胜(变成操作为奇数的局面),所以我们证明成立。
在上面,我们也证明了当操作数是偶数的时候,先手是必败的。
2 years ago
2 years ago
**情况$2$:有数量为$1$的堆**
这个情况比较复杂,因为如果某个人把$1$减少$1$,那么这个堆同时也消失了,相当于操作数减少了$2$。
2 years ago
* 假设有$a$堆石子,其中每堆石子个数为$1$
* 剩余堆的石子个数都严格大于$1$
2 years ago
2 years ago
根据这些数量大于$1$的堆的石子可以求出上述定义出的$b$,我们使用$f(a, b)$表示此时先手必胜还是必败,因为博弈论在本质上是可以递推的,我们可以想出起点,再想出递推关系,就可以递推得到更大数据情况下的递推值,也就是博弈论本质上是$dp$。
2 years ago
2 years ago
<center><img src='https://cdn.acwing.com/media/article/image/2021/05/15/61813_30ead721b5-image-20210515211400052.png'></center>
2 years ago
2 years ago
2 years ago
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312261053532.png)
2 years ago
2 years ago
2 years ago
### 六、实现代码
2 years ago
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
2 years ago
const int N = 55, M = 50060;
int n, f[N][M];
2 years ago
int dfs(int a, int b) {
2 years ago
if (~f[a][b]) return f[a][b]; // 记忆化搜索
int &v = f[a][b]; // 引用,赋值更快捷
if (!a) return b & 1; // a==0时看b是不是奇数奇数必胜否则必败
if (b == 1) return dfs(a + 1, b - 1);
if (a && !dfs(a - 1, b)) return v = 1; // 从左边取一石子:左侧不空,并且取完后整体是一个必败态,那我必胜
if (b && !dfs(a, b - 1)) return v = 1; // 合并b
if (a && b > 1 && !dfs(a - 1, b + 1)) return v = 1; // 合并a,b各一个
if (a > 1 && !dfs(a - 2, b == 0 ? b + 2 : b + 3)) return v = 1; // 合并a
2 years ago
return v = 0;
}
int main() {
2 years ago
int T;
2 years ago
cin >> T;
2 years ago
memset(f, -1, sizeof f); // 初始化DP数组-1
// 边界初始化
f[1][0] = f[2][0] = 1;
f[3][0] = 0;
2 years ago
while (T--) {
cin >> n;
2 years ago
int a = 0, b = -1;
for (int i = 1; i <= n; i++) {
2 years ago
int x;
cin >> x;
2 years ago
if (x == 1)
a++; // 左侧石子个数为1的石子堆数量
2 years ago
else
2 years ago
b += x + 1; // 1:新增加1堆x:这一堆x个
2 years ago
}
2 years ago
if (b < 0) b = 0; // b=0
2 years ago
if (dfs(a, b))
puts("YES");
else
puts("NO");
}
return 0;
}
2 years ago
```