题目链接: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);
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<=m;i++){ int a,b;scanf("%d%d",&a,&b); if(solve(a,b))printf("Yes\n"); else printf("No\n"); } return 0; }
|