题目链接:https://uoj.ac/problem/51

做法

这道题目主要涉及到了一个转化的问题。

SG 函数一般是使用在了没得走的时候为必败态,但是如果题目是走到了某个状态就输了要怎么转化呢?

答案是,我们认为玩家绝顶聪明,那显然不会去走那些输了的状态,也就是状态树中只会去走那些不会立即输的状态。(对应在本题中就是走后一定要 $a^b\le n$)

那么显然,当一个点没的走的时候,其是不是就一定输了,因为其下一步必定要走到判定为输的局面,这样我们就转化为了 $SG$ 的常见模型:没得走的状态为必败态。

这道题目也是同理,经过这种转化后,就是一个非常裸的 SG 函数了。

可以直接按题解写的做:$O(\sqrt{n}\log{n})$ ,但是感觉也存在更加优秀的做法。

就是观察发现状态只有 $0,1,2$ 三种,而且 $2$ 不会相邻,观察不难发现,同一个 $b$ 至多一个 $2$ ,记录一下 $2$ 的位置以及同一个 $b$ 的 SG 函数的一些参数,应该是可以做到 $O(\text{预处理}+m\log{n})$ 的时间做出来这道题目的。

但是感觉上就非常难写,以及有很多细节,有兴趣的读者可以自行尝试,我就不尝试了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 110000
#define M 40
using namespace std;
int n,m,a[M][N];
int b[M],top;
bool ksm(int x,int y){
int now=n;
while(y){
if(y&1){
if(now<x)return 0;
now/=x;
}
if(y!=1 && now/x<x)return 0;
x=x*x;y>>=1;
}
return 1;
}
int solve(int x,int y){
if(y==1 && x>b[2])return (n-x)&1;
else return a[y][x];
}
int findans(int x,int y){
int now=0;
while(now==x || now==y)now++;
return now;
}
int main(){
scanf("%d%d",&n,&m);

// printf("%d\n",ksm(2,31));

b[top=1]=n;
while(b[top]>1){
b[++top]=1;
while(ksm(b[top]+1,top))b[top]++;
}
top--;
for(int i=top;i>=1;i--){
for(int j=min(b[i],b[2]);j>=2;j--){
int x=-1,y=-1;
if(!(i==top || j>b[i+1]))x=a[i+1][j];
if(j!=b[i] && !(i==1 && j>b[2]))y=a[i][j+1];
a[i][j]=findans(x,y);
}
}
// for(int i=1;i<=top;i++){
// printf("%d %d: ",i,b[i]);
// for(int j=2;j<=b[i];j++)printf("%d ",a[i][j]);
// printf("\n");
// }
for(int i=1;i<=m;i++){
int a,b;scanf("%d%d",&a,&b);
if(solve(a,b))printf("Yes\n");
else printf("No\n");
}
return 0;
}