题目大意
有一种石子游戏,爱丽丝先手,爱丽丝和鲍勃轮流操作。有两堆石子,每次可以从一堆石子中取出k(k>0)个,并且从另一堆石子中取出s×k(s≥0)个。谁不能进行操作谁就输了。如果爱丽丝和鲍勃足够聪明,给出石子数量,问谁会赢。数据范围:每堆石子数量在0~5000之间,有10000次以内的询问。
样例输入
5
2 3
3 5
5 7
7 5
7 7
样例输出
Bob
Alice
Bob
Bob
Alice
思路
这是一道博弈问题,在纸上推算一定数目的样例找不到规律,因此使用SG函数利用计算机的高性能推演出所有的可能情况。设先手必胜的点为必胜点,后手必胜的点为必败点,则(0,0)为必败点。
由SG第一定理(一个局面的所有后继状态都是必胜态,则此局面为必败态)知,可以从必败点(0,0)开始,根据取石子的游戏规则,推导出其后继所有的必胜点(类似动态规划)。用暴力的方式,枚举推导完所有的状态,刚好不会超时。
注意不需要使用memset初始化SG数组,否则超时。这是由于暴力的时间复杂度为 O ( n 2 l o g n ) O(n^2logn) O(n2logn),n取5000时不考虑常数因子就几乎超过 1 0 8 10^8 108,再使用memset就又增加 2.5 × 1 0 7 2.5×10^7 2.5×107,故不用,编译器会自动初始化SG函数为0,即必败态。
考点
SG函数
枚举和暴力
AC代码
#include<iostream>
using namespace std;
const int maxn = 5e3 + 2;
bool sg[maxn][maxn];
void get_sg() {
for (int i = 0; i <= 5000; i++) {
for (int j = 0; j <= 5000; j++) {
if (sg[i][j] == 0) {
for (int k = 1; i + k <= 5000; k++) {
for (int s = 0; j + s*k <= 5000; s++) {
sg[i + k][j + s*k] = 1;
}
}
for (int k = 1; j + k <= 5000; k++) {
for (int s = 0; i + s*k <= 5000; s++) {
sg[i + s*k][j + k] = 1;
}
}
}
}
}
}
int t, n, m;
int main()
{
get_sg();
cin >> t;
while (t--) {
cin >> n >> m;
sg[n][m] == 0 ? puts("Bob") : puts("Alice");
}
return 0;
}
心得
本题考察博弈论,SG函数是博弈问题中常用的一种经典解题方法,需要熟练掌握。