题目大意:
给定两堆石头,分别有n块和m块,有两个人开始拿石头,每人每次要从一堆石头中拿k(k>0)个,从另一堆中拿s*k个(s>=0),Alice为先手,最后谁不能再拿石头,谁就输了,如果Alice赢就输出"Alice",反之输出"Bob"。
解题思路:设置数组f[n][m],表示两堆石子分别有n个和m个时,最终的游戏结果,我们令f[n][m]=1表示此时先手必赢,f[n][m]=0表示先手必输,如果我们根据游戏过程来计算该数组会发现不太好算,即从大石子堆变成小石子堆的计算过程是比较困难的,所以我们这里我们尝试从小到大来递推。
初始状态f[0][0]=0,意思是如果此时Alice面对的是两堆石子数都是0的石子堆,那么Alice必输,所以我们可以从该状态开始从小到大递推,即在一堆石子加上k个石头,另一堆加上s*k个石头,递推出来的所有状态都是先手必赢的状态,所以可以遍历找到所有必输的状态,然后从该状态出发,递推得到从该状态出发可以得到的所有必赢的状态,那这里有个问题就是如果当前我们遍历到的数组状态是0,那该状态下的游戏结果是否和数组表示的状态一致?答案是肯定的,这一点就非常类似于数论中的素数筛,如果当前状态是0,说明在它之前所有的比当前石子堆规模小的状态不能递推得到当前状态,所以当前状态一定不是先手必赢局面,又因为该游戏不可能会有平局的情况产生,所以该状态是一定是先手必输的状态。
这个题比较坑的一点就是数组的规模要开的非常精确,刚好开到给定的数据范围就能过,遍历数组的时候最多比给定范围多3,再多就T了。。。。。。
上代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N =5e3+10;
bool f[N][N];
int m,n,t;
int main()
{
for(int i=0;i<=5000;i++)
for(int j=0;j<=5000;j++)
if(f[i][j]==0)
{
for(int k=1;k+i<N;k++)
for(int s=0;j+s*k<N;s++)
f[i+k][j+s*k]=1;
for(int k=1;k+j<N;k++)
for(int s=0;i+s*k<N;s++)
f[i+s*k][j+k]=1;
}
cin>>t;
while(t--)
{
scanf("%d %d",&n,&m);
printf("%s\n", f[n][m] ? "Alice" : "Bob");
}
return 0;
}