[题目][蓝桥杯PREV-14] 高僧斗法
一、题目
0、题目链接
http://lx.lanqiao.cn/problem.page?gpid=T37(需要登录且需要 VIP 账户)
1、问题描述
古时丧葬活动中经常请高僧做法事。仪式结束后,有时会有“高僧斗法”的趣味节目,以舒缓压抑的气氛。
节目大略步骤为:先用粮食(一般是稻米)在地上“画”出若干级台阶(表示 N 级浮屠)。又有若干小和尚随机地“站”在某个台阶上。最高一级台阶必须站人,其它任意。(如图1所示)
两位参加游戏的法师分别指挥某个小和尚向上走任意多级的台阶,但会被站在高级台阶上的小和尚阻挡,不能越过。两个小和尚也不能站在同一台阶,也不能向低级台阶移动。
两法师轮流发出指令,最后所有小和尚必然会都挤在高段台阶,再也不能向上移动。轮到哪个法师指挥时无法继续移动,则游戏结束,该法师认输。
对于已知的台阶数和小和尚的分布位置,请你计算先发指令的法师该如何决策才能保证胜出。
2、输入格式
输入数据为一行用空格分开的 N 个整数,表示小和尚的位置。台阶序号从 1 算起,所以最后一个小和尚的位置即是台阶的总数。(N < 100, 台阶总数 < 1000)
3、输出格式
输出为一行用空格分开的两个整数: A B, 表示把 A 位置的小和尚移动到 B 位置。若有多个解,输出 A 值较小的解,若无解则输出 -1。
4、样例输入
1 5 9
5、样例输出
1 4
二、分析与思路
引入一个经典的 Nim 博弈游戏(Nim Game):有 n 个石堆,每个石堆有若干个石子,两个人轮流任意一堆取任意多的石子,每次至少取一个,取走最后石子的人获胜。
这个博弈游戏的结论是:n 个石堆的石子个数全部作异或运算的结果为 0 时,表示当前状态先手必败。
而这道题是 Nim 游戏的变式。
博弈的过程往往漫长而繁琐,而且一般情况下两人均会采用最优策略,所以分析博弈过程的关键一点在于如何将中间大量能被互相抵消的步骤析出。如本题,我们将小和尚两两分组(如果是奇数,则最后一个小和尚落单),对于任意一组小和尚 a[i], a[i + 1],如果先手将 a[i] 往后移动 x 格(保证在 a[i + 1] 之前),而后手将 a[i + 1] 往后移动 x 格(保证在 a[i + 2] 之前),反之亦然,则两人的操作是无后效性的。而这个过程,可以转换为 Nim 游戏,即:将每一组的小和尚的台阶差 b[i] 视作石堆的石子个数,每一次对 a[i] 往后 x 格的移动,都相当于 b[i] -= x, b[i - 1] += x,其中 b[i - 1] 非组内小和尚的台阶差,即实际上等价于从第 i 堆石堆中取出 x 个石子。
所以,我们最开始就求出上述台阶差,如果满足 Nim 博弈游戏的结论,即异或和为 0,则说明先手必败,输出 -1;反之,由于题目只要求先手法师的第一步操作(这一点题目似乎并没有表述得很清楚),那么我们按照字典序枚举法师的所有可能的操作,并在每次操作后均计算一次异或和,直到结果为 0,说明后手必败(即对于后手者而言先手必败),输出结果即可。
三、代码
#include <bits/stdc++.h> using namespace std; #define MAXN 1005 int a[MAXN], tot, o, b[MAXN]; int main() { while (!cin.eof()) cin >> a[++tot]; tot--; for (int i = 1; i < tot; i++) b[i] = a[i + 1] - a[i] - 1; for (int i = 1; i < tot; i += 2) o ^= b[i]; if (o) for (int i = 1; i < tot; i++) { int t = b[i]; for (int j = 1; j <= t; j++) { b[i - 1]++, b[i]--; o = 0; for (int k = 1; k < tot; k += 2) o ^= b[k]; if (!o) cout << a[i] << ' ' << a[i] + j, exit(0); } b[i - 1] -= t, b[i] += t; } cout << -1; }
四、相关知识点
博弈论