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

本文介绍了一种解决特定组合计数问题的方法,通过质因数分解将问题转化为状态压缩动态规划。针对2到n的整数集合,将其划分为两个子集,确保任意两个数互质,通过巧妙的状态划分和转移实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:把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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值