题目链接:https://ac.nowcoder.com/acm/contest/11166/A
题目:
给你两堆石子,每个人可以从某一堆中取出k个(k > 0),从另一堆中取出s * k个 (s >= 0),
现Alice先去,Bob后取,到哪个人取不了了以后,哪个人就算输。
思路:
首先我们知道,当面对{0,0}的情况的时候,必为输, 而在此基础上可以知道,f[0 + k][0 + s * k] 或者 f[0 + s * k][0 + k] 必赢。 同理,f[2][3]必为输,所以f[2 + k][3 + s * k] / f[2 + s * k][3 + k]必为赢,因为f[2 + k][3 + s * k]必能直接变为f[2][3]的情况。
以此类推。
于是进行初始化,f[i][j] 中 i 从 0~ 5000, j 从0~5000。同时还要枚举 i + k 和 j + s * k 的情况,
其中 i + k <= 5000, j + s * k <= 5000. 进行初始化。
那么初始化完了之后呢?
给出两堆石子a,b ,当f[a][b]为1的时候,那么就可以确定其为必胜态了。 那么问题来了,f[a][b]为0的时候,是否就一定代表其会输呢? 这个是肯定的.无法使得f[a - k][b - s * k]也变为0.
因为如果f[a - k][b - s * k]为0的话,则 f[a - k + k][b - s * k + s * k]必为1.因为取走f[a - k + k][b - s * k + s * k]中取走 {k,s * k}即可,变为0. 所以相互矛盾.从而可以知道从f[a][b] == 0所能得到的值必为1.
那么还有一个问题:这样的话, 时间复杂度不久在0(n ^ 4)了嘛?必定过不了才对.
然而实际上并不是这样的.
有这么一个结论需要知道,对于一个i只存在至多一种j后手能够获胜.
证明如下,如果f[i][j] 满足后手胜,f[i][t] 也满足后手胜, 且 t > j的话, 那么当f[i][t]的情况时,我们应该是后手胜,但是我们可以发现我们从第二堆中取出 t - j 个 则变为了f[i][t] 又变成了一个新的后手胜. 这就产生了矛盾. 所以可以知道 对于一个i 至多只存在一种j后手胜.,
那么这个结论有什么用呢?这个结论的用处就是每个i只有 最多一个胜利的状态, 那么我们就不需要对所以的状态进行处理了. 通过无法取胜的状态去 更新能够取胜的状态 可以大大降低时间复杂度, 变为O(n).
则总的时间复杂度就由 O(n ^ 4)变为了O(n ^ 3 左右了).
代码实现:
// 此代码时间卡的特别紧
# include <iostream>
using namespace std;
const int N = 5010;
bool f[N][N];
int t;
int main()
{
f[0][0] = 0; // 输
for(int i = 0 ; i <= 5000 ; i++)
{
for(int j = 0 ; j <= 5000; j++)
{
if(f[i][j] == 0) // 使用输的状态去更新赢的状态,虽然内层有两个for循环,但是赢得状态总的不会超过5000.
{
for(int k = 1 ; i + k <= 5000 ; k++)
{
for(int s = 0 ; j + s * k <= 5000 ; s++)
{
f[i + k][j + s * k] = 1;
}
}
for(int k = 1 ; j + k <= 5000 ; k++)
{
for(int s = 0 ; i + s * k <= 5000 ; s++)
{
f[i + s * k][j + k] = 1;
}
}
}
}
}
scanf("%d",&t);
while(t--)
{
int a,b;
scanf("%d %d",&a,&b);
if(f[a][b])
{
printf("Alice\n");
}
else
{
printf("Bob\n");
}
}
return 0;
}