https://www.luogu.com.cn/problem/P2150
状压dp
首先两个人选的数两两互质可以转化为没有公共的质因数,那么先把 ([2,n]) 做质因数分解,30分暴力就简单了
直接设 (f(i,S,T)) 表示考虑前 (i) 个数,两人选的质因数集合分别时 (S,T),的方案数
设 (k) 为当前数的质因数集合,转移就是 (f(i-1,S,T)
ightarrow f(i,S|k,T),(k operatorname{and} T=0)) 以及 (f(i-1,S,T)
ightarrow 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)
ightarrow f1(S|k,T),(koperatorname{and} T=0)) 以及 (f2(S,T)
ightarrow f2(S,T|k),(koperatorname{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
",ans);
return 0;
}