【洛谷P2150】寿司晚宴

题目

题目链接:https://www.luogu.com.cn/problem/P2150
为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。
在晚宴上,主办方为大家提供了 \(n-1\) 种不同的寿司,编号 \(1,2,3,\ldots,n-1\),其中第 \(i\) 种寿司的美味度为 \(i+1\)。(即寿司的美味度为从 \(2\)\(n\)
现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 \(x\) 的寿司,小 W 品尝的寿司中存在一种美味度为 \(y\) 的寿司,而 \(x\)\(y\) 不互质。
现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 \(p\) 取模)。注意一个人可以不吃任何寿司。
\(n\leq 500,p\leq 10^{10}\)

思路

\(500\) 以内一共有 \(95\) 个质数,不好状压。
我们发现这其中每一个数超过 \(\lfloor\sqrt{500}\rfloor=22\) 的质因子只会有一个。我们设 \(d_i\) 为数字 \(i\) 超过 \(22\) 的质因子。然后把数字按照 \(d_i\) 排序。
显然这样对于每一段 \(d_i\) 相同的,这些数字只能由同一个人拿。
这样剩余的不超过 \(22\) 的质数就只有 \(8\) 个了。考虑状压。把每一段分别 dp。设 \(f[0/1][i][j][k]\) 表示选到第 \(i\) 个数,第一个人的质因子集合是 \(j\),第二个人的质因子集合是 \(k\) 时,这一段由第一个人还是第二个人拿的方案数。转移就枚举上一次的状态。预处理出每一个数的不超过 \(22\) 的质因子集合。空间可以以把 \(i\) 这一维滚动
注意当处理完一段之后需要合并。设 \(g[i][j]\) 表示处理完一段后,第一个人质因子集合为 \(i\),第二个人为 \(j\) 的方案数。然后与 \(f\) 合并就可以了。
时间复杂度 \(O(2^{16}n)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int prm[8]={2,3,5,7,11,13,17,19};
const int N=510,M=(1<<8);
int n,a[N],s[N],d[N];
ll ans,MOD,f[2][2][M][M],g[M][M];

bool cmp(int x,int y)
{
	return d[x]<d[y];
}

int main()
{
	scanf("%d",&n);
	scanf("%lld",&MOD);
	for (int i=2;i<=n;i++)
	{
		int p=i;
		for (int j=0;j<=7;j++)
			if (!(p%prm[j]))
			{
				s[i]|=(1<<j);
				while (!(p%prm[j])) p/=prm[j];
			}
		d[i]=p; a[i]=i;
	}
	sort(a+2,a+1+n,cmp);
	g[0][0]=1;
	for (int l=2;l<=n;l++)
	{
		int id=(l&1),i=a[l];
		if (d[i]!=d[a[l-1]] || d[i]==1)
		{
			for (int j=0;j<M;j++)
				for (int k=0;k<M;k++)
				{
					if (l!=2)
						g[j][k]=(f[0][id^1][j][k]+f[1][id^1][j][k]-g[j][k])%MOD;
					f[0][id^1][j][k]=f[1][id^1][j][k]=g[j][k];
				}
		}
		for (int j=0;j<M;j++)
			for (int k=0;k<M;k++)
			{
				f[0][id][j][k]=f[0][id^1][j][k];
				f[1][id][j][k]=f[1][id^1][j][k];
			}
		for (int j=0;j<M;j++)
			for (int k=0;k<M;k++)
			{
				if (!(k&s[i])) (f[0][id][j|s[i]][k]+=f[0][id^1][j][k])%=MOD;
				if (!(j&s[i])) (f[1][id][j][k|s[i]]+=f[1][id^1][j][k])%=MOD;
			}
	}
	int id=(n&1);
	for (int i=0;i<M;i++)
		for (int j=0;j<M;j++)
		{
			g[i][j]=(f[0][id][i][j]+f[1][id][i][j]-g[i][j])%MOD;
			ans=(ans+g[i][j])%MOD;
		}
	printf("%lld",(ans%MOD+MOD)%MOD);
	return 0;
}
posted @ 2021-02-20 19:55  stoorz  阅读(84)  评论(0编辑  收藏  举报