您的位置:首页 > 其它

BZOJ4197: [Noi2015]寿司晚宴 状压DP

2017-04-11 13:54 323 查看
题意:把2~n这些数分成两个可空集合,要求两个集合中任取一对数都必须互质,求方案数

2≤n≤500

比较显然的是可以把每个数分解质因数,相当于两个集合中不能存在同一质因数。

但是质数个数可能很多,并不好设状态。

注意到每个数中最多只有一个大于等于根号n的质因子,而小于根号n的质数只有8个,那么将所有数按照大质因子分类,规定每一类最多只能被一个集合选,就可以把前八个质数状压来表示状态了。

注意没有大质因子的数每个都要单成一类而不是放在同一类,因为互相并不影响。

那么可以考虑一下如何转移,设f[i][j] 表示当前已经有多少种方案使得第一个集合状态为i,第二个集合状态为j。对于一个新的类,分别枚举把这个类给第一个集合与给第二个集合两种情况。具体的,若给第一个集合,先令g[i][j]=f[i][j], 再枚举这一类中的每个元素进行g[i|x][j]+=g[i][j] 的转移,给第二个集合同理。最后,由于两种转移是平行的,因此新的f[i][j]是将两者的g[i][j]相加,又由于两个都不给的情况在二者中都出现了,因此还要再减去原来的f[i][j]。 最后累加所有i&j==0的f[i][j]即可。

代码:

#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
typedef unsigned int u;
int pri[]={2,3,5,7,11,13,17,19};
vector<u> v[501];
int n,p;
int f[1<<8][1<<8],g1[1<<8][1<<8],g2[1<<8][1<<8];
u maxn=(1<<8)-1;
int main()
{
scanf("%d%d",&n,&p);
for(int i=2;i<=n;++i)
{
u k=0;
int x=i;
for(int j=0;j<8;++j)
{
while(x%pri[j]==0)
{
k|=(1u<<j);
x/=pri[j];
}
}
if(x==1) v[i].push_back(k);
else v[x].push_back(k);
}
f[0][0]=1;
for(int i=1;i<=n;++i)
if(!v[i].empty())
{
memcpy(g1,f,sizeof g1);
memcpy(g2,f,sizeof g2);
for(vector<u>::iterator __it=v[i].begin(),__end=v[i].end();__it!=__end;++__it)
{
u x=*__it;
for(u j=maxn;~j;--j)
for(u k=maxn;~k;--k)
{
(g1[j|x][k]+=g1[j][k])%=p;
(g2[j][k|x]+=g2[j][k])%=p;
}
}
for(u j=0;j<=maxn;++j)
for(u k=0;k<=maxn;++k)
f[j][k]=((u)g1[j][k]+g2[j][k]+p-f[j][k])%p;
}
int ans=0;
for(u i=0;i<=maxn;++i)
for(u j=0;j<=maxn;++j)
if((i&j)==0)
(ans+=f[i][j])%=p;
printf("%d\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: