CCPC2020 网络预选赛 0005 Lunch(SG博弈+质因数分解)
题意
给\(n\)个数\(a_1,a_2,...,a_n\),有两个选手,依次操作,对于一个数\(a_i\),每个人可以选择\(a_i\)的一个因数\(k\),将\(a_i\)分为\(k\)个\(\frac{a_i}{k}\),如果一个人不能操作了,那么他就输了。问先手是否必赢。
题解
如果学过sg函数,那么结果就是\(sg(a_1)\oplus sg(a_2)\oplus ...\oplus sg(a_n)\)是否为真值。那么来考虑一下\(sg(x)\)的值,设\(d_1,d_2,...,d_k\)为\(x\)的因数,那么\(x\)可以到达的状态就是\(d_1\)个\(\frac{x}{d_1}\),\(d_2\)个\(\frac{x}{d_2}\),……,\(d_k\)个\(\frac{x}{d_k}\),\(sg(x)\)的值就是这几个状态的异或和。对于\(d_i\)个\(\frac{x}{d_i}\)这个状态,它的\(sg\)值就是\(d_i\)个\(sg(\frac{x}{d_i})\)的异或和。如果\(d_i\)是偶数,那么这个状态的\(sg\)值就是\(0\);否则这个状态的\(sg\)值就是\(sg(\frac{x}{d_i})\)。根据这个结果,可以写一个暴力求\(sg\)函数来解决这个问题的程序:
int sg(int x){
// cout<<"x="<<x<<endl;
if(vis[x]) return f[x];
unordered_map<int,bool> cnt;
for(int i=1;i*i<=x;i++){
if(x%i!=0) continue;
int a=i,b=x/i;
if(b%2==0||a==1) cnt[0]=1;
else cnt[sg(a)]=1;
if(i==x/i||i==1) continue;
swap(a,b);
if(b%2==0||a==1) cnt[0]=1;
else cnt[sg(a)]=1;
}
for(int i=0;;i++)
if(!cnt[i]){
vis[x]=1;
return f[x]=i;
}
}
当然这个程序是会超时的,可以打表看看每个数的\(sg\)值,可以发现:如果\(x=2^{b_0}a_1^{b_1}a_2^{b_2}...a_k^{b_k}\),那么\(sg(x)=[b_0>0]+b_1+b_2+...+b_k\)。所以直接把这个\(sg\)函数的解法换成质因数分解就可以了。
代码
int n,a[11];
int prime[N],vis[N],cnt;
int sg(int x){
vector<PII> vec;
for(int i=1;i<=cnt&&prime[i]*prime[i]<=x;i++){
if(x%prime[i]!=0) continue;
vec.push_back({prime[i],0});
while(x%prime[i]==0) vec.back().y++,x/=prime[i];
}
if(x>1) vec.push_back({x,1});
int res=0;
for(PII p:vec)
if(p.x==2) res++;
else res+=p.y;
return res;
}
void Solve(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int res=0;
for(int i=1;i<=n;i++) res^=sg(a[i]);
printf("%s\n",res?"W":"L");
}
int main(){
for(int i=2;i<=1e5;i++){
if(!vis[i]) {prime[++cnt]=i;vis[i]=1;}
for(int j=1;j<=cnt&&prime[j]*i<=1e5;j++){
vis[prime[j]*i]=1;
if(i%prime[j]==0) break;
}
}
int T;scanf("%d",&T);
while(T--) Solve();
return 0;
}