题目:https://www.luogu.org/problemnew/show/P2148
SG函数+找规律。
普通地用SG函数做。
每两堆是一个独立问题。因为虽然有可能一个人在同一组里连续操作2次,但操作一次一定会把一个必败状态改为必胜状态,不会需要连续操作两次。
关键是怎么快速求SG函数。
打表找规律:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int sg[25][25]; void dfs(int x,int y) { if(sg[x][y]!=-1)return; int sum=0; for(int i=1;i<x;i++) { dfs(i,x-i); sum|=(1<<sg[i][x-i]); } for(int i=1;i<y;i++) { dfs(i,y-i); sum|=(1<<sg[i][y-i]); } for(int i=0;i<=20;i++) if((sum&(1<<i))==0) { sg[x][y]=i;sg[y][x]=i; return; } } int main() { memset(sg,-1,sizeof sg); sg[1][1]=0; for(int i=1;i<=20;i++) { for(int j=1;j<=20;j++) { dfs(i,j); printf("%3d",sg[i][j]); } printf(" "); } return 0; }
然后发现 i 和 j 的规律:
SG=1:i % 2 ==1 && j % 2 ==1;
SG=2:i % 4 == 1,2 && j % 4 == 1,2;
SG=3:i % 8 == 1,2,3,4 && j % 8 == 1,2,3,4;
……
所以有了那个log的算法。
仔细一看,那个就是求 i 和 j 的第一个公共0在第几位,所以又有了O(1)的式子。
但是那个O(1)的式子有一个点过不去,是把NO输出成YES,不知何故。
#include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std; const ll N=2e4+5; ll T,n,a[2],sum; ll sg(ll a,ll b) { for(ll i=0,tmp=2;;i++,tmp<<=1) if((a-1)%tmp<(tmp>>1)&&(b-1)%tmp<(tmp>>1)) return i; // ll k=((a-1)|(b-1)); // k=((k+1)&(-k-1)); // return k-1; } int main() { scanf("%lld",&T); while(T--) { scanf("%lld",&n);sum=0; for(ll i=1;i<=n;i++) { scanf("%lld",&a[i&1]); if(!(i&1))sum^=sg(a[0],a[1]); } if(sum)printf("YES "); else printf("NO "); } return 0; }