题目
把2∼n2\sim n2∼n分成两个不必非空的集合S1,S2S_1,S_2S1,S2,问有多少种方法使gcd(∏S1,∏S2)=1gcd(\prod S_1,\prod S_2)=1gcd(∏S1,∏S2)=1
分析
那么很容易想到状态压缩dp,设dp[S1][S2]dp[S_1][S_2]dp[S1][S2]表示第一个集合为S1S_1S1,第二个集合为S2S_2S2的方案数,那么dp[S1∣k][S2]+=dp[S1][S2][S2dp[S_1|k][S_2]+=dp[S_1][S_2][S_2dp[S1∣k][S2]+=dp[S1][S2][S2 and k=0]k=0]k=0],S2S_2S2也类似,最后把所有答案都累加,但是这样还是有点问题,因为这样时间复杂度太大了,我们考虑优化,500以内最多有一个大质因数,所以可以处理每个数所存在的大质数,对它们进行大到小排序,要把数组分为三个部分,f1f1f1也就是选择S1S_1S1,f2f2f2也就是选择S2S_2S2,dpdpdp也就是总选择方案,那么首先若所含的大质数与上一个不同或不含大质数,那么就把f1,f2f1,f2f1,f2赋值为dpdpdp,否则接着上一个,然后f1,f2f1,f2f1,f2按照同样的方法,然后赋值给dpdpdp的时候必然是不能重复计算f1,f2f1,f2f1,f2,所以不能是同一个大质数或不含大质数,就可以了,但是发现f1,f2f1,f2f1,f2都是由dpdpdp得到的,所以按照容斥定理,dp=f1+f2−dpdp=f1+f2-dpdp=f1+f2−dp,只有8个小质数,所以时间复杂度是O(216n)O(2^{16}n)O(216n)
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#define rr register
using namespace std;
const int prime[8]={2,3,5,7,11,13,17,19};
struct rec{
int w,big,dat;
inline void init(){
rr int t=w;
for (rr int i=0;i<8;++i)
if (t%prime[i]==0){
dat|=1<<i;
while (t%prime[i]==0) t/=prime[i];
}
big=t;
}
bool operator <(const rec &t)const{
return big<t.big||(big==t.big&&w<t.w);
}
}a[501];
int f1[261][261],f2[261][261],dp[261][261],n,mod,ans;
inline void modd(int &a,int b){
a=a+b; a=(a<0)?(a+mod):a;
a=(a>=mod)?(a-mod):a;
}
signed main(){
scanf("%d%d",&n,&mod);
for (rr int i=1;i<n;++i) a[i].w=i+1,a[i].init();
sort(a+1,a+n); dp[0][0]=1;
for (rr int i=1;i<n;++i){
if (a[i].big!=a[i-1].big||a[i].big==1){
memcpy(f1,dp,sizeof(f1));
memcpy(f2,dp,sizeof(f2));
}
for (rr int j=255;~j;--j)
for (rr int k=255;~k;--k){
if (j&k) continue;
if (!(a[i].dat&j)) modd(f2[j][a[i].dat|k],f2[j][k]);
if (!(a[i].dat&k)) modd(f1[j|a[i].dat][k],f1[j][k]);
}
if (a[i].big!=a[i+1].big||a[i].big==1)
for (rr int j=255;~j;--j)
for (rr int k=255;~k;--k)
if (!(j&k))
modd(dp[j][k]*=-1,f1[j][k]+f2[j][k]);
}
for (rr int j=255;~j;--j)
for (rr int k=255;~k;--k)
if (!(j&k)) modd(ans,dp[j][k]);
return !printf("%d",(ans+mod)%mod);
}