题意:
两个人从 2 2 2 到 n n n 共 n − 1 n-1 n−1个数字中各选一些(允许不选),使得选出来的两个集合中不存在一对数满足, x ∈ A , y ∈ B x\in A,y\in B x∈A,y∈B 且 g c d ( x , y ) ≠ 1 gcd(x,y)\ne 1 gcd(x,y)=1,求合法的分配方案数
数据范围: 1 ≤ n ≤ 500 1\le n\le 500 1≤n≤500
分析:
题目可以转化为选出两个集合使得,每个集合的质因数集合没有交集
- 30pt
n ≤ 30 n \le 30 n≤30 的情况下质因数集合里的数不会超过10个,采用状压的方式,我们记 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑到第 i i i个数时甲集合质因数集合状态为 j j j,乙集合质因数集合状态为 k k k的方案数,预处理出 n n n以内每个数质因数分解后的状态,转移方程就是
if( w&k == 0) f[i][j][k] += f[i-1][j|w][k];
if( w&j == 0) f[i][j][k] += f[i-1][j][k|w];
滚动数组优化掉一维,复杂度为 O ( n × 2 20 ) O(n\times 2^{20}) O(n×220)
- 100pt
当 n n n变大之后质因数增多所以无法直接状压,但我们观察发现500这个数字给的很巧妙,因为500以内的每个数最多只有一个大于19的质因数,所以我们把每个数大于19的质因数拿出来记录一下,将大质因数相同的数放在一个集合里面,因为他们不能同时被甲乙选择,对于每一个集合内部,我们按照上面30pt的方法进行转移,具体来说就是记三个数组:
d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前两个质因数集合的状态为 i , j i,j i,j
f 1 [ i ] [ j ] f1[i][j] f1[i][j]表示让乙不选大质因数的情况下,两个质因数集合状态为 i , j i,j i,j
f 2 [ i ] [ j ] f2[i][j] f2[i][j]表示让甲不选大质因数的情况下,两个质因数集合状态为 i , j i,j i,j
对于集合内部的转移:
if( w&j == 0) f1[i][j] += f1[i|w][j];
if( w&i == 0) f2[i][j] += f2[i][k|j];
对于集合之间的转移就是
dp[i][j]=f1[i][j]+f2[i][j]-dp[i][j]
减掉 d p [ i ] [ j ] dp[i][j] dp[i][j]是因为 f 2 , f 1 f2,f1 f2,f1都包含两人都不选的情况,会重复计算一次
答案就是统计所有的 d p [ i ] [ j ] dp[i][j] dp[i][j],复杂度为 O ( n × 2 16 ) O(n\times 2^{16}) O(n×216)
代码:
#include<bits/stdc++.h>
using namespace std;
namespace zzc
{
int n,mod;
int p[10]={0,2,3,5,7,11,13,17,19,0};
int dp[300][300],f1[300][300],f2[300][300];
struct node
{
int val,big,s;
void init()
{
int tmp=val;
big=-1;
for(int i=1;i<=8;i++)
{
if(tmp%p[i]) continue;
s|=(1<<i-1);
while(tmp%p[i]==0) tmp/=p[i];
}
if(tmp!=1) big=tmp;
}
}a[510];
inline bool cmp(node a,node b)
{
return a.big<b.big;
}
void work()
{
scanf("%d%d",&n,&mod);
for(int i=2;i<=n;i++) a[i-1].val=i,a[i-1].init();
sort(a+1,a+n,cmp);
dp[0][0]=1;
for(int i=1;i<n;i++)
{
if(i==1||a[i].big!=a[i-1].big||a[i].big==-1)
{
memcpy(f1,dp,sizeof(f1));
memcpy(f2,dp,sizeof(f2));
}
for(int j=255;j>=0;j--)
{
for(int k=255;k>=0;k--)
{
if(j&k) continue;
if((a[i].s&j)==0) f2[j][k|a[i].s]=(f2[j][k|a[i].s]+f2[j][k])%mod;
if((a[i].s&k)==0) f1[j|a[i].s][k]=(f1[j|a[i].s][k]+f1[j][k])%mod;
}
}
if(i==n-1||a[i].big!=a[i+1].big||a[i].big==-1)
{
for(int j=0;j<=255;j++)
{
for(int k=0;k<=255;k++)
{
if(j&k) continue;
dp[j][k]=(f1[j][k]+(f2[j][k]+mod-dp[j][k])%mod)%mod;
}
}
}
}
long long ans=0;
for(int j=0;j<=255;j++)
{
for(int k=0;k<=255;k++)
{
if((j&k)==0&&dp[j][k]) ans=(ans+dp[j][k])%mod;
}
}
printf("%lld\n",ans);
}
}
int main()
{
zzc::work();
return 0;
}