题意:有一个数组与Mojtaba与Arpa两个玩家,每人每次可以选一个素数p与一个正数k,至少选取一个数组中能被整除的数,把他们替换成原来的数与
的商,如果某个玩家无法做出这样的操作,那么他就输了。游戏从mojtaba开始,轮流操作。
博弈论优先考虑sg函数,并且重中之重是局面是什么?两个局面必须是独立的,因为唯一分解定理,在这里我们可以把所有有关素数的元素的集合看成一个局面,更进一步,我们应该关心的是这个素数的所有幂次方有没有出现,所有幂次方的出现与否就是一个集合,枚举所有可能的幂次方,我们就能获得这个局面能转移到的所有局面。
这个局面一般是能用数字表示的,而最小素数2的30次方就已经是大于1e9的了,所以我们可以状态压缩来表示这个素数的幂的出现情况。如果用数组哈希记录素数的幂,那么空闲空间比较多,并且所有素数都查找一变有点浪费时间,所以考虑用map<int,int>存储。
既然需要分解素数,那么筛素数是必不可少的,元素的范围是1e9,要分解某个数,只需要用
以下的素数即可,证明如下:
先证明:这个素数的大于的因数最多仅有一个:
假设有至少两个大于的因数
,那么
,即这两个因数的原数必定大于k,矛盾,所以原命题是正确的。
既然如此,我们就能保证大于的因数最多一个,那么我们把小于
的素数考虑了,
最后不是1时,k本身就是那个大于
的素因数;k最后如果是1,就证明没有大于
的因数,就完成了唯一分解。
这题元素大小,所以只需要筛出
以内的数组就可以了,这个数不好计算,就筛出
以内的就可以了。
其次就是sg函数值的计算,按照定义,我们需要找出所有能转移到的局面,计算这些局面的mex即答案了。
在这里举一个样例,求局面的sg函数值,这个局面出现了4次幂,8次幂
枚举可以对这个局面使用的幂,按上面的分析,最多存在30次幂,如何判断枚举的这个幂是否可行,只需要看在
位及更高为是否存在1,也就是
,把这个数右移
位,剩下的就都是
位及其更高位了,那么只需要判断这个数不是0就可以了。
那么转移后的局面是什么?
还是以上面的局面位例子,假设枚举的幂为
我们能转移的是:所有大于等于的位右移
位,其余不动。因为原来
次幂的变为
,然后在移动后的位置 或运算 即可
那么小于位的状态是
,意思是先计算一个二进制
,在小于
位的所有位计为1,然后或运算就保留了原来的所有1。
最后就是转移的状态了,意思为,原来小于
的状态 或上 原来大于等于
位右移后的状态。
之后就是sg异或起来就是答案了
#include<bits/stdc++.h>
using namespace std;
int read()
{
int ret=0,base=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') base=-1;
ch=getchar();
}
while(isdigit(ch))
{
ret=(ret<<3)+(ret<<1)+ch-48;
ch=getchar();
}
return ret*base;
}
int n,prime[100005],cnt;
bool isprime[100005];
void Prime()
{
memset(isprime,true,sizeof(isprime));
for(int i=2;i<=100000;i++)
{
if(isprime[i]) prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<=100000;j++)
{
isprime[i*prime[j]]=false;
if(i%prime[j]==0) break;
}
}
}
map<int,int>v,sg;
void div(int k)
{
for(int i=1;i<=cnt&&k>1;i++)
{
if(k%prime[i]==0)
{
int t=0;
while(k%prime[i]==0)
{
k/=prime[i];
t++;
}
v[prime[i]]|=1<<t-1;//记录prime[i]出现的幂t,在二进制t位或,也就是1<<i-1
}
}
if(k>1) v[k]|=1;//分解到最后还不是1,那么最后那个就是大于sqrt(k)的素数了
}
int get_sg(int k)
{
if(!k) return 0;
if(sg.count(k)) return sg[k];
int temp=k;
vector<bool>vis(10005);
for(int i=1;i<=30;i++)//枚举对这个局面的操作,/=prime^i
{
if(k>>i-1)//如果枚举的这个幂i合法
{
//小于i位的状态
int temp1=((1<<i-1)-1)&k;
vis[get_sg((k>>i)|temp1)]=true;
}
}
//计算mex
int ret=0;
while(vis[ret]) ret++;
return sg[k]=ret;
}
int main()
{
n=read();
Prime();int ans=0;
for(int i=1;i<=n;i++) div(read());
for(auto i:v) ans^=get_sg(i.second);
if(ans) cout<<"Mojtaba";
else cout<<"Arpa";
return 0;
}