Codeforces 850C SG函数与状态压缩,分解某个数需要多大范围的数

本文讨论了在Mojtaba和Arpa的博弈游戏中,如何通过状态压缩和唯一分解定理来分析局面,利用素数幂的出现情况和sg函数计算胜率。关键步骤包括筛选素数、分解因数、状态转移以及sg值的求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:有一个数组与Mojtaba与Arpa两个玩家,每人每次可以选一个素数p与一个正数k,至少选取一个数组中能被p^{k}整除的数,把他们替换成原来的数与p^{k}的商,如果某个玩家无法做出这样的操作,那么他就输了。游戏从mojtaba开始,轮流操作。

博弈论优先考虑sg函数,并且重中之重是局面是什么?两个局面必须是独立的,因为唯一分解定理,在这里我们可以把所有有关素数p的元素的集合看成一个局面,更进一步,我们应该关心的是这个素数的所有幂次方有没有出现,所有幂次方的出现与否就是一个集合,枚举所有可能的幂次方,我们就能获得这个局面能转移到的所有局面。

这个局面一般是能用数字表示的,而最小素数2的30次方就已经是大于1e9的了,所以我们可以状态压缩来表示这个素数的幂的出现情况。如果用数组哈希记录素数的幂,那么空闲空间比较多,并且所有素数都查找一变有点浪费时间,所以考虑用map<int,int>存储。

既然需要分解素数,那么筛素数是必不可少的,元素的范围是1e9,要分解某个数k,只需要用\sqrt{k}以下的素数即可,证明如下:

先证明:这个素数的大于\sqrt{k}的因数最多仅有一个:

假设有至少两个大于\sqrt{k}的因数r1,r2,那么r1*r2>\sqrt{k}*\sqrt{k}=k,即这两个因数的原数必定大于k,矛盾,所以原命题是正确的。

既然如此,我们就能保证大于\sqrt{k}的因数最多一个,那么我们把小于\sqrt{k}的素数考虑了,k最后不是1时,k本身就是那个大于\sqrt{k}的素因数;k最后如果是1,就证明没有大于\sqrt{k}的因数,就完成了唯一分解。

这题元素大小\leq 10^{9},所以只需要筛出10^{4.5}以内的数组就可以了,这个数不好计算,就筛出10^{5}以内的就可以了。

其次就是sg函数值的计算,按照定义,我们需要找出所有能转移到的局面,计算这些局面的mex即答案了。

在这里举一个样例,求局面k=(10001000)_{2}的sg函数值,这个局面出现了4次幂,8次幂

枚举可以对这个局面使用的幂,按上面的分析,最多存在30次幂,如何判断枚举的这个幂i是否可行,只需要看在i位及更高为是否存在1,也就是if(1>>i-1),把这个数右移i-1位,剩下的就都是i位及其更高位了,那么只需要判断这个数不是0就可以了。

那么转移后的局面是什么?

还是以上面的局面位例子,假设枚举的幂为i=4

我们能转移的是:所有大于等于i的位右移i位,其余不动。因为原来t 次幂的变为t-i(t\leq i),然后在移动后的位置 或运算 即可

那么小于i位的状态是((1<<i)-1)\&k ,意思是先计算一个二进制((1<<i)-1),在小于i位的所有位计为1,然后或运算就保留了原来的所有1。

最后(((1<<i)-1)\&k)|(k>>i)就是转移的状态了,意思为,原来小于i的状态 或上 原来大于等于i位右移后的状态。

 之后就是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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值