[bzoj4197] [NOI2015]寿司晚宴
状压\(dp\).
对于\(30\%\)的数据,\(n\leq 30\),质因数的种类最多只有10个,所以可以压缩状态,设状态\(sta\)为第\(i\)位为集合中是否有第\(i\)个质因数。
设\(f[i][s1][s2]\)表示当前考虑到第\(i\)个数,第一个集合状态为\(s1\),第二个集合状态为\(s2\)的方案数。
设当前枚举到的数状态为\(k\),显然有转移:\(f[i][s1|k][s2]+=f[i-1][s1][s2]\)或\(f[i][s1][s2|k]+=f[i-1][s1][s2]\).
这种刷表转移,根据套路,可以逆循环省掉第一维,时间复杂度\(O(n*2^{20})\).
然后对于所有数据,上面的算法就不行了。但是看一眼数据范围:\(n\leq 500\),还是很小。
可以发现500的范围最多只能有一个大于22的质数,而小于22的质数只有8个。于是先按这个大一点的质数排序,然后一个一个质数的处理。
设\(f[i][s1][s2]\)表示当前做到第\(i\)个质数了,前8个质数的状态为\(s1,s2\)的方案数。注意没有比22大的质数的数算一类。
对于当前枚举到的质数\(p\),设\(g[0/1][s1][s2]\)表示状态为\(s1,s2\),当前质数p给第一个/第二个人,且算上质数p及以前的质数的方案数,然后\(g\)的初值可以设为上一个质数的\(f\).这表示不含当前质数的方案数。然后\(g\)的转移可以按照30分的方法转移。
当枚举到含有质数p的最后一个数时,转移完\(g\)时可以利用\(g\)来更新\(f\):
因为\(g[0]\)和\(g[1]\)都含有不选质数p的情况,所以要减去一个。
然后发现\(f\)的第一维还是可以省掉,时间复杂度\(O(n*2^{16})\)。
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define int long long
void read(int &x) {
x=0;int f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);x*=f;
}
void print(int x) {
if(x<0) x=-x,print(x/10);
if(!x) return ;print(x/10);putchar((x%10)^48);
}void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');}
int n,f[300][300],g[300][300][2];
int pri[]={0,2,3,5,7,11,13,17,19};
struct node {
int p,s;
void init(int x) {
int num=x;
for(int i=1;i<=8;i++)
if(!(num%pri[i])) {
s|=1<<(i-1);
while(!(num%pri[i])) num/=pri[i];
}p=num;
}
bool operator < (const node x) const {return p<x.p;}
}a[1000];int mod;
void out(int x) {
char s[10];
//itoa(x,s,2);
printf("%s\n",s);
}
int add(int x,int y) {x+=y;if(x>mod) x-=mod;if(x<0) x+=mod;return x;}
signed main() {
read(n),read(mod);for(int i=2;i<=n;i++) a[i].init(i);
//for(int i=2;i<=n;i++) out(a[i].s);
sort(a+2,a+n+1);f[0][0]=1;
for(int i=2;i<=n;i++) {
if(a[i].p!=a[i-1].p||a[i].p==1||i==2)
for(int s1=0;s1<=255;s1++)
for(int s2=0;s2<=255;s2++)
g[s1][s2][0]=g[s1][s2][1]=f[s1][s2]%mod;
for(int s1=255;~s1;s1--)
for(int s2=255;~s2;s2--) {
if(!(a[i].s&s1)) g[s1][s2|a[i].s][1]=(g[s1][s2|a[i].s][1]+g[s1][s2][1])%mod;
if(!(a[i].s&s2)) g[s1|a[i].s][s2][0]=(g[s1|a[i].s][s2][0]+g[s1][s2][0])%mod;
}
if(a[i].p!=a[i+1].p||a[i].p==1||i==n)
for(int s1=0;s1<=255;s1++)
for(int s2=0;s2<=255;s2++)
f[s1][s2]=((g[s1][s2][0]+g[s1][s2][1]-f[s1][s2])%mod+mod)%mod;
}
int ans=0;
for(int s1=0;s1<=255;s1++)
for(int s2=0;s2<=255;s2++)
if(!(s1&s2)) ans=(ans+f[s1][s2])%mod;
write(ans);
return 0;
}