P2150 [NOI2015]寿司晚宴
https://www.luogu.com.cn/problem/P2150
状压dp
首先两个人选的数两两互质可以转化为没有公共的质因数,那么先把 \([2,n]\) 做质因数分解,30分暴力就简单了
直接设 \(f(i,S,T)\) 表示考虑前 \(i\) 个数,两人选的质因数集合分别时 \(S,T\),的方案数
设 \(k\) 为当前数的质因数集合,转移就是 \(f(i-1,S,T)\rightarrow f(i,S|k,T),(k \operatorname{and} T=0)\) 以及 \(f(i-1,S,T)\rightarrow f(i,S,T|k),(k \operatorname{and} S=0)\)
考虑正解,因为 \(22<sqrt(500)<23\),所以每个数都至多有一个大于 \(22\) 的质因数,如果单独考虑这个大的质因数,剩下需要考虑的小质因数就只有 \(8\) 个了
可以把所有数按大质因数分段,把所有大质因数相同的数分进一个段里,对于没有大质因数的数就自己一段,然后只要求出每个段的答案再合并就好了
设 \(f1(S,T),f2(S,T)\) 分别是考虑到当前段,把这个大质数分给第一/二个人,两人所取质因数集合分别为 \(S,T\) 的方案数。\(dp(S,T)\) 则为总共的方案数
每当进入一个新段,要把 \(dp\) 的值分别赋值给 \(f1,f2\)
合并的话只要 \(dp(S,T)=f1(S,T)+f2(S,T)-dp(S,T)\) 就行,要减是因为 \(f1,f2\) 中会把在原来的基础上两人都不选任何数这个方案计算两编,贡献就是原来的 \(dp(S,T)\),减去他
考虑 \(f1,f2\) 之间的转移,设 \(k\) 为当前数的小质因数集合,可知有 \(f1(S,T)\rightarrow f1(S|k,T),(k\operatorname{and} T=0)\) 以及 \(f2(S,T)\rightarrow f2(S,T|k),(k\operatorname{and} S=0)\)
其实和之前的暴力做法是有些相似的,只不过是在限定了大质数要给谁的基础上
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN puts("")
inline long long read(){
register long long x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=getchar();}
return y?x:-x;
}
#define N 300
int n;
long long mod;
long long f1[N][N],f2[N][N],dp[N][N];
const int prime[8]={2,3,5,7,11,13,17,19};
struct Node{
int val,S,big;
inline void init(){
for(reg int i=0;i<8;i++){
if(val%prime[i]) continue;
S|=(1<<i);
while(!(val%prime[i])) val/=prime[i];
}
if(val>1) big=val;
}
}a[505];
inline int cmp(Node a,Node b){return a.big<b.big;}
int main(){
n=read();mod=read();
for(reg int i=1;i<n;i++) a[i].val=i+1,a[i].init();
std::sort(a+1,a+1+n,cmp);
dp[0][0]=1;
for(reg int i=1;i<n;i++){
if(i==1||a[i].big!=a[i-1].big||!a[i].big){
std::memcpy(f1,dp,sizeof f1);std::memcpy(f2,dp,sizeof f2);
}
for(reg int j=255;~j;j--)for(reg int k=255;~k;k--){//滚动数组,逆序
if(j&k) continue;
if(!(a[i].S&j)) f2[j][k|a[i].S]=(f2[j][k|a[i].S]+f2[j][k])%mod;
if(!(a[i].S&k)) f1[j|a[i].S][k]=(f1[j|a[i].S][k]+f1[j][k])%mod;
}
if(i==n||a[i].big!=a[i+1].big||!a[i].big){
//一段结束,合并答案,减掉重复计算的两人都不选的情况数
for(reg int j=0;j<256;j++)for(reg int k=0;k<256;k++)
if(!(j&k)) dp[j][k]=(f1[j][k]+f2[j][k]-dp[j][k]+mod)%mod;
}
}
long long ans=0;
for(reg int i=0;i<256;i++)for(reg int j=0;j<256;j++)
if(!(j&i)) ans=(ans+dp[i][j])%mod;
printf("%lld\n",ans);
return 0;
}