题目链接:传送门
游戏规则:
没次能够将一堆分成两堆 x = a*b (a!=1&&b!=1)x为原来堆的个数,a,b为新堆的个数。
也能够将原来的堆的个数变成原来堆的约数y。y!=x。进行最后一次操作的人获胜。
分析:
也是一个去石头的游戏,因此我们仅仅须要将全部情况的sg值异或起来就好了。
我们首先来考虑一堆。设这一堆的个数为x;
那么全部的情况就是
(a1,x/a1), (a2,x/a2),...,(an,x/an);或者(a1),(a2),..,(an)。
由于数据量比較大,我们朴素的找约数肯定会超时。
然后细致分析一下这个问题。由于我
们都是环绕着约数来进行操作。那么也就相当于在对他的素因子的个数进行操作。
x=a1^r1*a2^r2*...*an^rn;设sum = r1+r2+...+rn.
然后全部的情况就能够表示为:
(1,sum-1),(2,sum-2),...(sum/2,sum-sum/2)或者(1),(2),...(n-1)
这样就大大减小了数据的范围。然后在计算sum的时候我们能够这样计算。
设一个数为x,他的最小的素因子为y.则sum[x] = sum[x/y] + 1;
代码例如以下:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 5000010;
int prime[maxn],cnt;
bool isprime[maxn];
int fac_num[maxn];
int min_fac[maxn];
int sg[100];
void GetPirme(){
cnt=0;
memset(isprime,0,sizeof(isprime));
memset(fac_num,-1,sizeof(fac_num));
memset(min_fac,-1,sizeof(min_fac));
for(int i=2;i<maxn;i++){
if(!isprime[i]){
prime[cnt++]=i;
for(int j=i+i;j<maxn;j+=i){
isprime[j]=1;
if(min_fac[j]==-1)
min_fac[j]=i;
}
min_fac[i]=i;
}
}
}
int get_num(int x){
if(x==1) return 0;
if(fac_num[x]!=-1) return fac_num[x];
return fac_num[x]=get_num(x/min_fac[x])+1;
}
int Get_Sg(int x){
if(sg[x]!=-1) return sg[x];
bool vis[100];
memset(vis,0,sizeof(vis));
for(int i=1;i<=x;i++) vis[Get_Sg(x-i)]=1;
for(int i=1;i<=x/2;i++)
vis[Get_Sg(i)^Get_Sg(x-i)]=1;
for(int i=0;;i++){
if(!vis[i]){
return sg[x]=i;
}
}
}
void init(){
GetPirme();
memset(sg,-1,sizeof(sg));
sg[0]=0;
}
int main()
{
init();
int n;
while(~scanf("%d",&n)){
int x ,ans=0;
for(int i=0;i<n;i++){
scanf("%d",&x);
//cout<<"num: "<<get_num(x)<<endl;
ans^=Get_Sg(get_num(x));
}
if(ans) puts("Alice");
else puts("Bob");
}
return 0;
}