2021牛客暑期多校第一场A题(爱丽丝和鲍勃)题解

题目大意

有一种石子游戏,爱丽丝先手,爱丽丝和鲍勃轮流操作。有两堆石子,每次可以从一堆石子中取出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^8108,再使用memset就又增加2.5 × 1 0 7 2.5×10^72.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函数是博弈问题中常用的一种经典解题方法,需要熟练掌握。


版权声明:本文为keguaiguai原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。