【BZOJ4197】[Noi2015]寿司晚宴 状压DP+分解质因数

【BZOJ4197】[Noi2015]寿司晚宴

Description

为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。

在晚宴上,主办方为大家提供了 n−1 种不同的寿司,编号 1,2,3,…,n−1,其中第 i 种寿司的美味度为 i+1 (即寿司的美味度为从 2 到 n)。
现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 x 的寿司,小 W 品尝的寿司中存在一种美味度为 y 的寿司,而 x 与 y 不互质。
现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 p 取模)。注意一个人可以不吃任何寿司。

Input

输入文件的第 1 行包含 2 个正整数 n,p,中间用单个空格隔开,表示共有 n 种寿司,最终和谐的方案数要对 p 取模。

Output

输出一行包含 1 个整数,表示所求的方案模 p 的结果。

Sample Input

3 10000

Sample Output

9

HINT

 2≤n≤500

0<p≤1000000000

题解:我们考虑每个素数,它要么在A中,要么在B中,要么都不在。我们定义<sqrt(500)(更具体的说是<=19,因为23*29>500)的质数为小质数,其余的为大质数,则2-n中的任意一个数都可以表示成1(或0)个大质数*若干个小质数。因此我们可以状压小质数,枚举大质数。

用g[0或1][x][y]表示当前的大质数在A或B中,A的小质数状态为x,B的小质数状态为y的方案数。我们预处理出对于每个大质数,可以和它搭配的 小质数组 有哪些(其实就是枚举一个大质数p的所有倍数,将所有倍数都分解质因数)。如果当前这个数要被A选,相当于A既要选这个大质数也要选这个 小质数组 ,并且B既不能选这个大质数也不能选这个 小质数组 中的任一个质数。我们设这个 小质数组 的状态为S,得到DP方程:

g[0][x|S][y]=g[0][x|S][y]+g[0][x][y]
g[1][x][y|S]=g[1][x][y|S]+g[1][x][y]

我们用f[x][y]统计答案,但是发现存在重复的情况。你可以理解为虽然这个数被A抢走了,使得B不能选这个数,但是A也不想选这个数。或者这个数被B抢走了,但是B也不想选。那么重复的方案数是多少呢?就是之前的f[x][y]。所以新的f[x][y]=g[0][x][y]+g[1][x][y]-旧的f[x][y]。

同时,对于不包含大质数的数,要特殊处理。对于不同的大质数,用乘法原则统计答案(因为A可以把好多大质数都抢走);对于不同的小质数选择方法,用加法原则统计答案(小质数不能既给A又给B);DP的时候要时刻满足x&y==0。(以上都是只有本蒟蒻才不明白的地方。)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
int n,mod,ans;
int f[1<<8][1<<8],g[2][1<<8][1<<8];
int p2[510];
int pri[]={2,3,5,7,11,13,17,19};
vector<int> v[510];
int getpri(int x)
{
	int i,ret=0;
	for(i=0;i<8;i++)
	{
		if(x%pri[i]==0)
		{
			ret|=(1<<i);
			while(x%pri[i]==0)	x/=pri[i];
		}
	}
	v[x].push_back(ret);
}
int main()
{
	scanf("%d%d",&n,&mod);
	int i,j,x,y;
	for(i=2;i<=n;i++)	getpri(i);
	f[0][0]=1;
	for(j=0;j<v[1].size();j++)
	{
		for(x=(1<<8)-1;~x;x--)
		{
			for(y=(1<<8)-1;~y;y--)	if(!(x&y))
			{
				if(!(y&v[1][j]))	f[x|v[1][j]][y]=(f[x|v[1][j]][y]+f[x][y])%mod;
				if(!(x&v[1][j]))	f[x][y|v[1][j]]=(f[x][y|v[1][j]]+f[x][y])%mod;
			}
		}
	}
	for(i=23;i<=n;i++)
	{
		if(!v[i].size())	continue;
		for(x=0;x<(1<<8);x++)	for(y=0;y<(1<<8);y++)	if(!(x&y))	g[0][x][y]=g[1][x][y]=f[x][y];
		for(j=0;j<v[i].size();j++)
		{
			for(x=(1<<8)-1;~x;x--)
			{
				for(y=(1<<8)-1;~y;y--)	if(!(x&y))
				{
					if(!(y&v[i][j]))	g[0][x|v[i][j]][y]=(g[0][x|v[i][j]][y]+g[0][x][y])%mod;
					if(!(x&v[i][j]))	g[1][x][y|v[i][j]]=(g[1][x][y|v[i][j]]+g[1][x][y])%mod;
				}
			}
		}
		for(x=0;x<(1<<8);x++)	for(y=0;y<(1<<8);y++)	f[x][y]=((g[0][x][y]+g[1][x][y]-f[x][y])%mod+mod)%mod;
	}
	for(x=0;x<(1<<8);x++)	for(y=0;y<(1<<8);y++)	ans=(ans+f[x][y])%mod;
	printf("%d",ans);
	return 0;
}
posted @ 2017-07-06 21:23  CQzhangyu  阅读(204)  评论(0编辑  收藏  举报