P2150 【NOI2015】寿司晚宴 状压DP+数学
题意:
两个人从 \(2\) 到 \(n\) 共\(n-1\)个数字中各选一些(允许不选),使得选出来的两个集合中不存在一对数满足,\(x\in A,y\in B\) 且 \(gcd(x,y)\ne 1\),求合法的分配方案数
数据范围:\(1\le n\le 500\)
分析:
题目可以转化为选出两个集合使得,每个集合的质因数集合没有交集
- 30pt
\(n \le 30\) 的情况下质因数集合里的数不会超过10个,采用状压的方式,我们记\(f[i][j][k]\)表示考虑到第\(i\)个数时甲集合质因数集合状态为\(j\),乙集合质因数集合状态为\(k\)的方案数,预处理出\(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\times 2^{20})\)
- 100pt
当\(n\)变大之后质因数增多所以无法直接状压,但我们观察发现500这个数字给的很巧妙,因为500以内的每个数最多只有一个大于19的质因数,所以我们把每个数大于19的质因数拿出来记录一下,将大质因数相同的数放在一个集合里面,因为他们不能同时被甲乙选择,对于每一个集合内部,我们按照上面30pt的方法进行转移,具体来说就是记三个数组:
\(dp[i][j]\)表示当前两个质因数集合的状态为\(i,j\)
\(f1[i][j]\)表示让乙不选大质因数的情况下,两个质因数集合状态为\(i,j\)
\(f2[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]
减掉\(dp[i][j]\)是因为\(f2,f1\)都包含两人都不选的情况,会重复计算一次
答案就是统计所有的\(dp[i][j]\),复杂度为\(O(n\times 2^{16})\)
代码:
#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;
}