牛客多校1
题目链接–Alice and Bob
大概概述一下题目意思:
Alice和Bob在玩游戏,有两堆石子,每堆石子有m和n个,每名选手每次可以从一堆中拿k个(k>0),再从另一堆拿s*k个(s>=0),当某一名选手无法操作(n=0,m=0)时该选手输,输出Alice赢或者Bob赢。
从题目可以看出来这是一道博弈题,但和之前学的几个Nim游戏及扩展的博弈模型不太一样,伤脑筋。
我们假设任一选手面对的两堆石子分别为i,j,f[i][j]就表示当前两堆石子造成游戏的输赢结果,当任一选手面对f[i][j]=0表示该选手必输,如果f[i][j]=1表示该选手必赢。
- 已知(0,0)时必输,所以f[0][0]=0。
- 如果先手面对k和sk两堆石子,先手可以通过从k个石子那堆拿走k,另一堆拿走sk实现后手面对剩余石子的状态为必输,f[k-k][sk-sk]=f[0][0]=0,所以对于先手来说(k,sk)必赢。
- 当我们对所有的f[k][sk]赋值为1后,剩余的f[i][j]表示当前石子(i,j)无法只通过一步到达f[0][0]=0,接着我们对最靠近(0,0)的所有f[i][j]=0(先手必败)进行扩展处理f[i+k][j+sk]=1(先手必胜),当先手面对f[i+k][j+sk]时可以通过f[i+k-k][j+sk-sk]=f[i][j]=0使对手处于必败态。比如(2,3)(2,3)无法通过一步直接到达(0,0)在(2,3)之前也不存在(2-k,3-sk)满足f[2-k][j-sk]=0,使得先手面对(2,3)时可以通过一定操作使后手面对f[2-k][3-sk]=0(必败态)
注意一下: f[i][j]表示的应该是任一选手面对(i,j)是必胜还是必败,但是题目是先手开始,所以直接定义f[i][j]=1先手胜,反之后手胜。
综上所述上f[i][j]=1时先手胜,否则后手胜。
必胜就是可以通过操作使对手处于必败态,必败就是对手一定可以通过操作使我处于必败态(当时听y总的课不是很理解,现在好像有点懂了)。
先手胜和后手胜仅用0,1表示,是通过扩展处理将结果传递,一个状态的前一个状态为必败,前一个状态通过操作当满足(+k,+sk)到当前状态,当前状态就一定能通过操作使对手处于上一个操作,必败态。
几个细节说明一下:
-
为什么当f[i][j]=0,当前选手任意取石子都无法使对手处于必败态f[i-k][j-sk]=0,反证,假设f[i-k][j-sk]=0可以成立,那么f[i-k+k][j-sk+sk]=1一定成立,矛盾,所以f[i-k][j-s*k]不可能为0,所以如果当前选手f[i][j]=0,无论怎么操作后手都不会处于必败态。
-
而且通过证明我们的到当一堆石子固定的时候另一堆石子只有一种情况满足先手必胜:
反证:假设一堆石子固定,另一堆石子存在两种情况满足后手必胜,即f[i][p]=0,f[i][q]=0,(p>q),那么f[i][p-(p-q)]=f[i][q]=1,矛盾。f[i][p-(p-q)]=f[i-0*(p-q)][p-(p-q)],这就是对先手获胜扩展处理的依据。
if (!f[i][j])
for (int k = 1; i + k <= N; k++)
for (int t = 0; j + k * t <= N; t++)
f[i+k][j+k*t] = 1;
#include<iostream>
using namespace std;
const int N = 5 * 1e3;
bool f[N+1][N+1];
int main()
{
for(int i=0;i<=N;i++)
for (int j = 0; j <= N; j++) {
if (!f[i][j]) {
for (int k = 1; i + k <= N; k++)
for (int t = 0; j + k * t <= N; t++)
f[i+k][j+k*t] = 1;
for (int k = 1; j + k <= N; k++)
for (int t = 0; i + k * t <= N; t++)
f[i+k*t][j+k] = 1;
}
}
int n;
cin >> n;
while (n--) {
int a, b;
scanf("%d %d",&a,&b);
if (f[a][b]) cout << "Alice" << endl;
else cout << "Bob" << endl;
}
return 0;
}
在懒散了好几天终于打算补一下之前的题了
呜呜我真是个大菜狗,又菜又爱玩