P2150 [NOI2015] 寿司晚宴 题解
P2150 [NOI2015] 寿司晚宴
首先容易想到,一种方案是 “和谐的” 当且仅当这两个人品尝的寿司集合中,不存在包含相同质因子的数。
考虑把所有质因子的存在情况压成一个二进制数,然后从 \(2\) 到 \(n\) 枚举每个数给谁,然后统计答案。具体实现上,可以设计三个 DP 数组:\(\mathit{dp}(i,j,k)\) 表示枚举到 \(i\) 这个数,两个人的状态分别为 \(j,k\) 的方案数,\(f(j,k)\) 和 \(g(j,k)\) 分别表示当两个人的状态为 \(j,k\) 时,把当前数给第一/二个人选的方案数。每次把 \(\mathit{dp}\) 数组的值拷给 \(f\) 和 \(g\),让 \(f\) 和 \(g\) 跑完之后,有 \(\mathit{dp}(i,j,k)=f(j,k)+g(j,k)-\mathit{dp}(i-1,j,k)\) 转移。(减去一个 \(\mathit{dp}\) 的原因是减去算重的两个人都不选当前数的情况,也就是原来的 \(\mathit{dp}(i-1,j,k)\))
显然 \(i\) 这一维是不必要的,那么这个算法的复杂度是 \(O(2^{\log^2n}n)=O(2^{20}n)\),时间上是能过去的。但问题在于 \(500\) 以内质因数的数量很多,我们没办法把它们压到一个二进制数里,所以上述做法只能拿到 30pts。
但是注意到,对于 \(500\) 以内的所有数,至多出现一种大于 \(19\) 的质因数,因为 \(23\times29\) 已经大于 \(500\)。
所以,我们把这个多出来的一种质因数拎出来,也就是说,以前我们按照 \(2\) 到 \(n\) 的顺序枚举,现在我们按照每个数大于 \(19\) 的最大质因数大小来从小到大枚举。对于最大质因数大于 \(19\) 且相同的所有数,我们把它们看作冲突的数就好了,只在最大质因数出现不同的时候执行上面对 \(\mathit{dp}\) 数组的转移。时间复杂度 \(O(2^{16}n)\),空间复杂度 \(O(2^{16})\)。
#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;
}

浙公网安备 33010602011771号