【BZOJ4197】【NOI2015】寿司晚宴(动态规划)

【BZOJ4197】【NOI2015】寿司晚宴(动态规划)

题面

BZOJ
([2,n])中选择两个集合(可以为空集),使得两个集合中各选一个数出来,都互质。
求方案数。

题解

对于(500)以内的所有数,它的最大质因子如果大于(sqrt n),那么便只有一个。
利用这一点,我们把所有数全部用小于(sqrt n)的质数来分解。
最后剩下的结果一定是一个(gt sqrt n)的质数或者(1),再乘上(le sqrt n)的质数。
小于(sqrt n)的质数很少,只有(8)个,可以按照这个进行状压。
显然,拥有大于(sqrt n)质因数的所有数可以归结为一类,并且他们只能一起放在一个集合中。
所以考虑状态(f[i][j])表示第一个人选择了质因数集合(i),第二个人选择了(j)
因为大于(sqrt n)的质因数放在一起考虑,所以不将他压进状态。
我们再枚举这个质因数放进哪个集合就好了,这个可以再诶外开数组维护。
当这个质因数的所有数都考虑完了之后,
当前的贡献就是(f[i][j]=g[0][i][j]+g[1][i][j]-f[i][j])
其中(g)数组表示把当前这个质因数给了第一个人还是第二个人,后面两维意义和(f)相同的方案数。
因为(f[i][j])是前面所有其他质因子的答案,当前在求和的时候存在没有将当前质因子的任何一个数放进任意一个集合,所以这里会被算两遍,把它减掉就好了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
int n,MOD,ans;
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
int f[1<<8][1<<8],g[2][1<<8][1<<8];
int p[8]={2,3,5,7,11,13,17,19};
pair<int,int> a[505];
int main()
{
	scanf("%d%d",&n,&MOD);
	for(int i=2;i<=n;++i)
	{
		int x=i;
		for(int j=0;j<8;++j)
			while(x%p[j]==0)x/=p[j],a[i].second|=1<<j;
		a[i].first=x;
	}
	sort(&a[2],&a[n+1]);f[0][0]=g[0][0][0]=g[1][0][0]=1;
	for(int z=2;z<=n;++z)
	{
		for(int x=255;~x;--x)
			for(int y=255;~y;--y)
				if(!(x&y))
				{
					if(!(y&a[z].second))add(g[0][x|a[z].second][y],g[0][x][y]);
					if(!(x&a[z].second))add(g[1][x][y|a[z].second],g[1][x][y]);
				}
		if(a[z].first==1||a[z].first!=a[z+1].first)
		{
			for(int i=0;i<1<<8;++i)
				for(int j=0;j<1<<8;++j)
					if(!(i&j))f[i][j]=(g[0][i][j]+g[1][i][j]-f[i][j])%MOD,add(f[i][j],MOD);
			memcpy(g[0],f,sizeof(g[0]));memcpy(g[1],f,sizeof(g[1]));
		}
	}
	for(int i=0;i<1<<8;++i)
		for(int j=0;j<1<<8;++j)
			if(!(i&j))add(ans,f[i][j]);
	printf("%d
",ans);
	return 0;
}

原文地址:https://www.cnblogs.com/cjyyb/p/9277825.html