P2150 [NOI2015] 寿司晚宴 题解

P2150 [NOI2015] 寿司晚宴

首先容易想到,一种方案是 “和谐的” 当且仅当这两个人品尝的寿司集合中,不存在包含相同质因子的数。

考虑把所有质因子的存在情况压成一个二进制数,然后从 2n 枚举每个数给谁,然后统计答案。具体实现上,可以设计三个 DP 数组:dp(i,j,k) 表示枚举到 i 这个数,两个人的状态分别为 j,k 的方案数,f(j,k)g(j,k) 分别表示当两个人的状态为 j,k 时,把当前数给第一/二个人选的方案数。每次把 dp 数组的值拷给 fg,让 fg 跑完之后,有 dp(i,j,k)=f(j,k)+g(j,k)dp(i1,j,k) 转移。(减去一个 dp 的原因是减去算重的两个人都不选当前数的情况,也就是原来的 dp(i1,j,k)

显然 i 这一维是不必要的,那么这个算法的复杂度是 O(2log2nn)=O(220n),时间上是能过去的。但问题在于 500 以内质因数的数量很多,我们没办法把它们压到一个二进制数里,所以上述做法只能拿到 30pts

但是注意到,对于 500 以内的所有数,至多出现一种大于 19 的质因数,因为 23×29 已经大于 500

所以,我们把这个多出来的一种质因数拎出来,也就是说,以前我们按照 2 到 n 的顺序枚举,现在我们按照每个数大于 19 的最大质因数大小来从小到大枚举。对于最大质因数大于 19 且相同的所有数,我们把它们看作冲突的数就好了,只在最大质因数出现不同的时候执行上面对 dp 数组的转移。时间复杂度 O(216n),空间复杂度 O(216)

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

using ll=long long;
constexpr int MAXN=505;
int n,p,ans;
vector<int>pri;
int xpr[MAXN];
bool isp[MAXN];
struct Node{
	int v,b,s;
	bool operator<(const Node&x)const{
		return b<x.b;
	}
}a[MAXN];
int dp[256][256],f[256][256],g[256][256];
void add(int&x,int y){
	x=x+y>=p?x+y-p:x+y;
}

void init(){
	for(int i=2;i<=n;i++){
		if(!isp[i]) pri.emplace_back(i),xpr[i]=i;
		for(auto j:pri){
			if(i*j>n) break;
			isp[i*j]=1;
			xpr[i*j]=j;
			if(i%j==0) break;
		}
	}
}
int gb(int x){
	if(x==2) return 0;
	if(x==3) return 1;
	if(x==5) return 2;
	if(x==7) return 3;
	if(x==11) return 4;
	if(x==13) return 5;
	if(x==17) return 6;
	return 7;
}

int main(){
	scanf("%d%d",&n,&p);
	init();
	for(int i=2,x;i<=n;i++){
		x=a[i].v=i;
		a[i].b=-1;
		while(x>1){
			if(xpr[x]<=19) a[i].s|=1<<gb(xpr[x]);
			else a[i].b=max(a[i].b,xpr[x]);
			x/=xpr[x];
		}
	}
	sort(a+2,a+n+1);
	dp[0][0]=1;
	for(int i=2;i<=n;i++){
		if(!~a[i].b||a[i].b!=a[i-1].b){
			memcpy(f,dp,sizeof(f));
			memcpy(g,dp,sizeof(g));
		}
		for(int j=255;~j;j--)
			for(int k=255;~k;k--){
				if(j&k) continue;
				if(!(a[i].s&j)) add(f[j][k|a[i].s],f[j][k]);
				if(!(a[i].s&k)) add(g[j|a[i].s][k],g[j][k]);
			}
		if(!~a[i].b||a[i].b!=a[i+1].b)
			for(int j=255;~j;j--)
				for(int k=255;~k;k--){
					if(j&k) continue;
					dp[j][k]=((f[j][k]+g[j][k])%p-dp[j][k]+p)%p;
				}
	}
	for(int i=0;i<=255;i++)
		for(int j=0;j<=255;j++){
			if(i&j) continue;
			add(ans,dp[i][j]);
		}
	printf("%d\n",ans);
	return 0;
}
posted @   Laoshan_PLUS  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示