P2150 [NOI2015] 寿司晚宴 题解

P2150 [NOI2015] 寿司晚宴
刚开始看错题了,推了一个与原题类似的 DP 方程,然后不会优化,笑了。

思路

首先看到 \(n\) 很小,然后质因子个数就更少了。
因此第一反应是将所有的质因子状压进一个状态里,然后互相判断有没有互质即可。
但是 500 以内的质因子个数并不少,有接近 100 个,因此这只是 30 分的暴力。

但是有一个比较常见的优化:每个数只会有一个大于 \(\sqrt n\) 的质因子。因此我们将这种因子称为“大质因子”。
我们预处理出每个数的大质因子是多少,将所有大质因子相同的数一起转移。
与暴力类似的,我们设 \(f1_{s1,s2},f2_{s1,s2}\) 分别表示所有大质因子相同的数只能第一个人选或不选的方案数,只能第二个人选或不选的方案数。(因为两个人不能同时选有相同大质因子相同的数)
注意,这里的 \(s1,s2\) 只压缩了“小质因子”。
然后假设当前枚举到第 \(i\) 个数,其所有小质因子的压缩为 \(s_i\),有转移

\[\begin{aligned} f1_{s1 \cup s_i,s2} \gets f1_{s1 \cup s_i,s2}+f1_{s1,s2},s2\cap s_i=\varnothing \\ f1_{s1 ,s2\cup s_i} \gets f1_{s1 ,s2\cup s_i}+f1_{s1,s2},s1\cap s_i=\varnothing \end{aligned} \]

然后设 \(dp_{s1,s2}\) 在大质因子不同的阶段间转移。每次一个阶段时将 \(dp\) 复制进 \(f1,f2\) 中,结束时用 \(f1,f2\) 更新 \(dp\) 即可。具体而言,结束时有

\[dp_{s1,s2}\gets f1_{s1,s2}+f2_{s1,s2}-dp_{s1,s2} \]

减掉一个 \(dp\) 是为了减掉重复的情况。答案就是 \(dp\) 的和。

code

注意可能有数没有质因子大于 \(\sqrt n\)。发现这个时候其质因子个数必定小于等于 \(8\),因此直接在 \(dp\) 数组上用暴力做即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=505;
int n,p,pri[N]={2,3,5,7,11,13,17,19},dp[N][N],f1[N][N],f2[N][N];
struct node{int s,mp;}a[N];
bool cmp(node x,node y){return x.mp<y.mp;}
void add(int &x,int y){x=(x+y+p)%p;}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>p;
	for(int i=2;i<=n;i++){
		int tmp=i;
		for(int j=0;j<8;j++){
			while(tmp%pri[j]==0) tmp/=pri[j],a[i].s|=(1<<j);
		}
		a[i].mp=tmp;
	}
	sort(a+2,a+n+1,cmp);int tl=2,tr=2;dp[0][0]=1;
	while(a[tr+1].mp==1) tr++;
	for(int i=tl;i<=tr;i++){
		for(int s1=255;s1>=0;s1--) for(int s2=255;s2>=0;s2--){
			if((s2&a[i].s)==0) add(dp[s1|a[i].s][s2],dp[s1][s2]);
			if((s1&a[i].s)==0) add(dp[s1][s2|a[i].s],dp[s1][s2]);
		}
	}
	for(int l=tr+1,r;l<=n;){
		r=l;while(a[r].mp==a[r+1].mp) r++;
		for(int s1=255;s1>=0;s1--) for(int s2=255;s2>=0;s2--) f1[s1][s2]=dp[s1][s2],f2[s1][s2]=dp[s1][s2];
		for(int i=l;i<=r;i++)for(int s1=255;s1>=0;s1--) for(int s2=255;s2>=0;s2--){
			if((s2&a[i].s)==0) add(f1[s1|a[i].s][s2],f1[s1][s2]);
			if((s1&a[i].s)==0) add(f2[s1][s2|a[i].s],f2[s1][s2]);
		}
		for(int s1=255;s1>=0;s1--) for(int s2=255;s2>=0;s2--) (dp[s1][s2]=f1[s1][s2]+f2[s1][s2]-dp[s1][s2]+p)%=p;
		l=r+1;
	}
	int ans=0;
	for(int s1=255;s1>=0;s1--) for(int s2=255;s2>=0;s2--) (ans+=dp[s1][s2])%=p;
	cout<<ans<<'\n';return 0;
}
posted @ 2025-04-16 16:33  all_for_god  阅读(26)  评论(1)    收藏  举报