*题解:P2150 [NOI2015] 寿司晚宴

原题链接

解析

根据题意,不妨将两人品尝的寿司种类视为两个集合。那么,一种方案和谐当且仅当两集合内的数含有的质因子组成的集合不存在交集。

于是自然地,可以设计出一个状态 \(f_{i,S_g,S_w}\) 表示前 \(i\) 个数,小 G 和小 W 所拥有的数字的质因子集合分别为 \(S_g\)\(S_w\) 的方案数。设 \(S_i\) 表示第 \(i\) 个数质因子集合,那么转移方程为:

\[\begin{align*} \large \large f_{i + 1,S_g \cup S_i,S_w}&=\large f_{i,S_g\cup S_i,S_w}\\ \large f_{i + 1,S_g,S_w \cup S_i}&=\large f_{i,S_g,S_w\cup S_i}\\ \large f_{i + 1,S_g \cup S_i,S_w}+&=\large f_{i,S_g,S_w}\\ \large f_{i + 1,S_g,S_w \cup S_i}+&=\large f_{i,S_g,S_w} \end{align*} \]

分别对应是否选,选给谁的情况。

转移复杂度为 \(O(2^{\pi(n)}\cdot n)\),其中 \(\pi(n)\) 表示小于等于 \(n\) 的质数个数。

然而,\(\pi(500)=95\),复杂度无法接受。考虑如何缩减状态中的质因子个数。事实上,一个小于等于 \(500\) 的数必定至多有一个大于等于 \(23\) 的质因子,否则其所有质因子乘起来会大于 \(500\),矛盾。而小于 \(23\) 的质因子个数只有 \(8\) 个,所以不妨将数字分为含有大于大于等于 \(23\) 的质因子以及不含有的两类。对于不含有的数字,在状态里只存这 \(8\) 个质因子是否选取。对于含有的数字,每个数只会对应一个质因子,所以按照质因子来选,一种质因子一旦被一方选中另一方就不能再选,对于选中的一方,剩余包含该质因子的数需要根据其小质因子的分布判断是否可选。为此,可以将大质因子相同的数堆在一起转移,一个数是否可选取决于是否有一方已经选取相同大质因子的数以及双方已经选取的数的小质因子集合,所以要将它们加到状态里。

具体地,设 \(g_{0/1,S_g,S_w}\) 表示将当前大质因子留给小 G/小 W 选,小 G 和小 W 所拥有的数字的质因子集合分别为 \(S_g\)\(S_w\) 的方案数。那么就有跟 \(f\) 类似的转移,之后需要将 \(g\) 合并回 \(f\),转移方程为:

\[f_{i,j} = g_{0,i,j} + g_{1,i,j} - f_{i,j} \]

减去的 \(f_{i,j}\) 代表重复计算的“什么都不选”的方案数。

时间复杂度 \(O(2^{\pi(\sqrt n)}\cdot n)\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 500 + 5,M = 260 + 5;
vector<int> v[N],num[N],pri,a;
int s[N],pos[N];
bool isp[N];
int f[M][M],g[2][M][M];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	memset(isp,true,sizeof(isp));
	int n,p;
	cin>>n>>p;
	for(int i=2;i<=n;i++){
		if(isp[i]) pri.push_back(i);
		else continue;
		for(int j=1;j * i<=n;j++){
			isp[i * j] = false;
			num[i * j].push_back(i);
		}
	}
	for(int i=0;i<pri.size();i++){
		pos[pri[i]] = i;
	}
	for(int i=2;i<=n;i++){
		if(num[i].back() >= 23) v[num[i].back()].push_back(i);
		else a.push_back(i);
		for(int j=0;j<num[i].size();j++)if(num[i][j] < 23){
			s[i] |= (1 << pos[num[i][j]]);
		}
	}
	f[0][0] = 1;
	for(int i=0;i<a.size();i++){
		int x = a[i];
		for(int j=(1 << 8) - 1;j >= 0;j--){
			for(int k=(1 << 8) - 1;k >= 0;k--)if(!(j & k)){
				if(!((j | s[x]) & k)){
					f[j | s[x]][k] = (f[j | s[x]][k] + f[j][k]) % p;
				}
				if(!(j & (k | s[x]))){
					f[j][k | s[x]] = (f[j][k | s[x]] + f[j][k]) % p;
				}
			}
		}
	}
	vector<int> b;
	for(int i=23;i<=n;i++){
		if(v[i].size()){
			memcpy(g[0],f,sizeof(f));
			memcpy(g[1],f,sizeof(f));
			for(int l=0;l<v[i].size();l++){
				int x = s[v[i][l]];
				for(int j=(1 << 8) - 1;j >= 0;j--){
					for(int k=(1 << 8) - 1;k >= 0;k--)if(!(j & k)){
						g[0][j | x][k] = (g[0][j | x][k] + g[0][j][k]) % p;
						g[1][j][k | x] = (g[1][j][k | x] + g[1][j][k]) % p;
					}
				}
			}
			for(int j=(1 << 8) - 1;j >= 0;j--){
				for(int k=(1 << 8) - 1;k >= 0;k--)if(!(j & k)){
					f[j][k] = (1ll * g[0][j][k] + g[1][j][k] + p - f[j][k]) % p;
				}
			}
		}
	}
	int res = 0;
	for(int j=0;j<(1 << 8);j++){
		for(int k=0;k<(1 << 8);k++)if(!(j & k)){
			res = (res + f[j][k]) % p;
		}
	}
	cout<<res;
	return 0;
}
posted @ 2026-04-15 18:10  yutar  阅读(13)  评论(0)    收藏  举报