[BZOJ4197][NOI2015]寿司晚宴
description
从\(2\)到\(n\)中选出两个不相交子集使得这两个集合中的任一对元素互质。问总方案数模\(mod\)的结果。\(n\le500\)。
sol
首先有\(30\%\)的部分分是\(n\le30\),考虑到\(30\)以内的质因数只有\(10\)个,因此可以状压\(dp\),即设\(f[s_1][s_2]\)表示第一个人选出的质因数集合为\(s_1\),第二个人选出的质因数集合为\(s_2\)的方案数。复杂度\(O(n2^{20})\)。
对于\(100\%\)的数据\(n\le500\)。首先可以发现一个这样的性质:每个数中含有至多一个大于等于\(23\)的质因数,那么也就是说大于等于\(23\)的质因数是不可能产生冲突的。所以我们就只需要考虑前\(8\)个字符,然后对于每一个大于等于\(23\)的质因数,枚举是哪个人选择这个质因数,然后对应的转移就行了。
对于不含大于等于\(23\)的质因数的数,可以当做它含有一个其他数都不含有的质因数,然后按照上述的方式转移,可以避免一些讨论。
复杂度是\(O(n2^{16})\)。
code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define pi pair<int,int>
#define mk make_pair
#define fi first
#define se second
const int N = 505;
int p[]={2,3,5,7,11,13,17,19};
int n,mod,f[N][N],g[2][N][N],ans;
pi a[N];
void add(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int main(){
scanf("%d%d",&n,&mod);
for (int i=2;i<=n;++i){
int x=i,y=0;
for (int j=0;j<8;++j)
while (x%p[j]==0) y|=1<<j,x/=p[j];
a[i]=mk(x,y);
}
sort(a+2,a+n+1);f[0][0]=g[0][0][0]=g[1][0][0]=1;
for (int t=2;t<=n;++t){
for (int i=255;~i;--i)
for (int j=255;~j;--j){
if ((j&a[t].se)==0) add(g[0][i|a[t].se][j],g[0][i][j]);
if ((i&a[t].se)==0) add(g[1][i][j|a[t].se],g[1][i][j]);
}
if (a[t].fi==1||a[t].fi!=a[t+1].fi){
for (int i=0;i<256;++i)
for (int j=0;j<256;++j)
f[i][j]=((g[0][i][j]+g[1][i][j]-f[i][j])%mod+mod)%mod;
memcpy(g[0],f,sizeof(g[0]));
memcpy(g[1],f,sizeof(g[1]));
}
}
for (int i=0;i<256;++i)
for (int j=0;j<256;++j)
if ((i&j)==0) add(ans,f[i][j]);
printf("%d\n",ans);
return 0;
}