AcWing 1322. 取石子游戏
一、状态定义
设 表示在 区间的左侧放上一堆数量为 的石子后,先手必败。
可以为,此时就已经是必败态了,前面什么也不用加。
... |
---|
即:, 为 先手必败局面。
二、 的存在性证明
( 同理,下同):
反证法:
假设不存在满足定义的 ,则对于任意非负整数 ,有形如:
都为必胜局面,记为 局面。
由于 为必胜局面,故从 局面 必然存在种一步可达必败局面。
若从最左边一堆中拿,因为假设原因,不可能变成必败局面,因为这样得到的局面仍形如 。
注意包括此行在内的接下来几行默认
左边拿没用,只能考虑从右边拿:
于是设 一步可达的(某个)必败局面为 ,显然有 。
由于 有无限个,但 只有 种——根据抽屉原理,必存在 满足 和 都是必败局面。但这两个必败局面之间实际一步可达,故矛盾,进而原命题成立。
三、 的唯一性证明
反证法:
假设 不唯一,则存在非负整数 ,使得 和 均为必败局面。而这两个必败局面之间实际一步可达,故矛盾,进而原命题成立。
四、状态转移
1、边界情况
对于两堆相同的石子,后手进行和先手对称的操作,你咋干我就咋干,我拿完,你瞪眼~
2、场景分析
- 边界情况:
- 变化方法:从左侧拿走一些石子或者从右侧拿走一些石子
- 让我们使用和来表示和,形成递推关系。
想像一个通用场景:假设现在前面动作都按要求整完了,问我们:本步骤,我们有哪些变化,根据这些变化,怎么样用前面动作积累下来的数据来完成本步骤数据变化的填充。这不就是动态规划吗?

3、推论1
有了上面谁的唯一性,得出一个有用的推论:
对于任意非负整数 ,为必胜局面。
4、推论2
为方便叙述,下文记 为 ,记 为 ,并令
若 则 ,此时 ,也就是说 和 都属于 ,故其它 满足 。
注:因,表示在[,]确定后,右侧为就能满足[,]这一段为先手必败,此时,左侧增加那堆,个数为就可以继续保持原来的先手必败,即。
4、分类讨论
-
( )
最简单的情况——根据 的定义,区间 本来就是必败局面,故 -
- ,即 ( )
- 结论:
-
证明:
即证 为必败局面。
由于最左边和最右边的两堆石子数量相同,后手可进行和先手对称的操作,后手必将获得一个形如 或 的局面,其中: 。
结合 和 的定义知这个局面必胜,即后手必胜,先手必败,证毕。
只有左侧为这个唯一值时,才是必败态,现在不是,而是,所以后手必胜,即先手必败。 -
注意上述证明的前提是 ,因此后续证明若使用 ,必须满足 (具体见后文)。
- ,即 ( )
-
结论:
-
证明:
即证 为 必败局面。- 若先手拿最左边一堆,设拿了以后还剩 个石子。
- 若 ,则后手将最右堆拿成 个石子(注意 ),保证左侧比右侧多个石子,就能回到 本身,递归证明即可。
- 若 ,则后手将最右堆拿完,根据 定义知此时局面必败。
- 若 ,则后手将最右堆拿成 个石子,由 知此时是必败局面。
- 若 ,此时最右堆石子数 满足 ,结合 定义知局面必胜。
- 若先手拿最右边一堆,设拿了以后还剩 个石子。
若 ,则后手将最左堆拿成 个石子,递归证明即可。
若 ,则后手将最左堆拿成 个石子,由 知此时是必败局面。
若 ,则后手将最左堆拿成 个石子,由 定义知此时局面必败。
- 若先手拿最左边一堆,设拿了以后还剩 个石子。
-
- ,即 ( )
-
-
,即 ( )
-
结论:
-
证明:
- 若先手拿最左边一堆,设拿了以后还剩 个石子。
- 若 ,则后手将最右堆拿成 个石子,保证左侧比右侧多个石子,就能回到 本身,递归证明即可。
- 若 ,则后手将最右堆拿成 个石子,由 知此时是必败局面。
- 若 ,则后手将最右堆拿成 个石子(注意 保证了此时最右堆石子个数 ),由 的定义知此时是必败局面。
- 若先手拿最右边一堆,设拿了以后还剩 个石子。
- 若 ,则后手将最左边一堆拿成 个石子(注意 ),递归证明即可。保证右侧比左侧多个石子。
- 若 ,则后手把最左堆拿完,根据 的定义可知得到了必败局面。
- 若 ,则后手将最左堆拿成 个石子,由 知此时是必败局面。
- 若 ,此时最左堆石子数量 满足 ,结合 定义知局面必胜。
- 若先手拿最左边一堆,设拿了以后还剩 个石子。
-
-
,即 ( )
-
结论:
-
证明:
设先手将其中一堆拿成了 个石子。-
若 ,后手将另一堆也拿成个,回到 ,递归证明。
-
若 ,后手把另一堆也拿成 个石子即可转 。
-
若 ,将另一堆拿成 或 个石子即可得到必败局面。
-
剩余的情况是 或 。
可以解决最左堆 ,最右堆 的情况
可以解决最左堆 ,最右堆 的情
况。
所以只需解决最左堆 和最右堆 的情况。而这两种情况直接把另一堆拿完就可以得到必败局面。
-
-
-
综上所述:
温馨提示:请看清楚 取不取等,乱取等是错的!
同理可求 。
回到原题,先手必败当且仅当 ,于是我们就做完啦!
时间复杂度 。
五、实现代码
#include <cstdio>
using namespace std;
const int N = 1010;
int n;
int a[N], l[N][N], r[N][N];
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int len = 1; len <= n; len++)
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
if (len == 1)
l[i][j] = r[i][j] = a[i];
else {
int L = l[i][j - 1], R = r[i][j - 1], x = a[j];
if (R == x)
l[i][j] = 0;
else if (x < L && x < R || x > L && x > R)
l[i][j] = x;
else if (L > R)
l[i][j] = x - 1;
else
l[i][j] = x + 1;
// 与上述情况对称的四种情况
L = l[i + 1][j], R = r[i + 1][j], x = a[i];
if (L == x)
r[i][j] = 0;
else if (x < L && x < R || x > L && x > R)
r[i][j] = x;
else if (R > L)
r[i][j] = x - 1;
else
r[i][j] = x + 1;
}
}
if (n == 1)
puts("1");
else
printf("%d\n", l[2][n] != a[1]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2019-07-05 开发一个代码的自动生成器,使用Jfinal4.3+Swagger+Sql
2019-07-05 定时任务不执行处理一例
2019-07-05 yum 异常解决一例
2019-07-05 数据池项目演示地址及帐号
2017-07-05 判断浏览器
2015-07-05 厦门海沧区磁盘只有1TB的解决方案