题目大意:
解题思路:给定n个数字,两个人轮流玩游戏,把这n个数字变成n的因子(不包含本身),最后全变成1就赢了,问你谁会赢,如果Alice赢了,把第一步选择的那一个数字的序号输出,如果有多种方案,输出序号小的。
解题代码:sg[1]=0;
sg[2]=mex{sg[1]}=1;
sg[3]=mex{sg[1]}=1;
sg[4]=mex{sg[2],sg[1]}=2;
sg[5]=mex{sg[1]}=1;
sg[6]=mex{sg[2],sg[3]}=2;
sg[7]=mex{sg[1],sg[7]}=1;
sg[8]=mex{sg[1],sg[2],sg[4]}=3;
..........................
发现 sg[x]为 x因子的个数。
证明:因为 如果一个为a, 则:sg[x]=sg[(x/a)*a]=mex{sg[1]....,sg[(x/a)]}=sg[(x/a)]+1;求出每个数字的sg后,只需要看sg的异或和,如果==0 和明显,输出Bob
否则,输出Alice,但是要输出Alice先走了哪一步,这里有用到了一个性质:只需要用合并后的SG值与每一堆SG值分别异或,看得到的结果是否小于原来该堆的SG值,如果小则可以取该堆。
这个性质暂时不是很懂,先用上,以后了解了会给出证明和过程。
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
const int maxn=5000010;
int n,sg[maxn];
int d[110000];
vector <int> prime;
bool isprime[maxn];
void ini(){
isprime[2]=true;
for(int i=3;i<maxn;i+=2) isprime[i]=true;
for(int i=3;i<maxn;i+=2){
for(int j=i;j<maxn/i;j+=2){
isprime[i*j]=false;
}
}
for(int i=2;i<=maxn;i++){
if(isprime[i]) prime.push_back(i);
}
}
int SG(int x){
if(sg[x]!=-1) return sg[x];
int ret=0,size=prime.size(),tmp=x;
for(int i=0;i<size && prime[i]*prime[i]<=x;i++){
while(x%prime[i]==0){
x/=prime[i];
ret++;
}
}
if(x>1) ret++;
return sg[tmp]=ret;
}
int main(){
ini();
memset(sg,-1,sizeof(sg));
int casen=0;
while(scanf("%d",&n)!=EOF){
int ans=0;
for(int i=0;i<n;i++){
scanf("%d",&d[i]);
ans^=SG(d[i]);
}
if(ans==0) printf("Test #%d: Bob\n",++casen);
else{
for(int i=0;i<n;i++){
if( ( ans^SG(d[i]) ) < SG(d[i]) ){
printf("Test #%d: Alice %d\n",++casen,i+1);
break;
}
}
}
}
return 0;
}