bzoj4197 [Noi2015]寿司晚宴(状压dp)

题目链接

分析:
首先我们可以考虑一下简单的dp
把每一个数质因数分解,显然两个人在选择寿司的时候,如果小G已经有了质因数X,则小W就不能选择包含X的寿司了
我们就可以设计一个比较暴力的dp
f[i][x][y] f [ i ] [ x ] [ y ] 表示到第 i i 个数,小G选择寿司的质因数状态为x,小W选择寿司的质因数状态为 y y
由于n<=500状态太多(95个素数),所以我们需要减少状态

500=22 500 = 22
考虑一个数最多有一个大于 500 500 的质因子,可以特殊考虑

而小于等于 500 500 的质因子只有8个: {2,3,5,7,11,13,17,19} { 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 }

小于等于 500 500 的质因子可以通过状压解决
而大于 500 500 的部分我们必须想办法解决冲突和重复计算的问题

将所有数包含的质因子处理出来,把每个数按照“大于 500 500 的质因子”排序,那么这个质因子相等的区间我们一起处理
显然这相等的一整个区间,必须分配给同一个人(或者两者都不选)

g[i][s1][s2][0/1] g [ i ] [ s 1 ] [ s 2 ] [ 0 / 1 ] 表示到第 i i 个数,第一个人选择的集合为s1,第二个人选择的集合为 s2 s 2 ,同时当前这个大于 500 500 的质因子放入第一个人/第二个人的方案数

我们按照之前的暴力dp方法解决掉 g[i][s1[s2][0/1] g [ i ] [ s 1 [ s 2 ] [ 0 / 1 ] 的转移

f[i][s1][s2]=g[i][s1][s2][0]+g[i][s1][s2][1]f[i][s1][s2] f [ i ] [ s 1 ] [ s 2 ] = g [ i ] [ s 1 ] [ s 2 ] [ 0 ] + g [ i ] [ s 1 ] [ s 2 ] [ 1 ] − f [ i ] [ s 1 ] [ s 2 ]
表示这两种情况相加,但是因为这个质因子两个都不放的情况计算了两次,所以需要减掉一次

ps: p s : 对于状态转移,我们可以用01背包的方式优化掉第一维

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

const int N=503;
int sshu[8]={2,3,5,7,11,13,17,19}; 
struct node{
    int S;           //小于sqrt(500)的质因数集合 
    int prime;       //大于sqrt(500)的质因数 
};
node a[N];
int n;
ll p,f[N][N],g[N][N][2];

int cmp(const node &a,const node &b) {
    return a.prime<b.prime;
}

int main() {
    scanf("%d%lld",&n,&p);

    for (int i=2;i<=n;i++) {
        int x=i;
        for (int j=0;j<8;j++)  
            if (x%sshu[j]==0) {
                a[i].S|=(1<<j);
                while (x%sshu[j]==0) x/=sshu[j];
            }
        a[i].prime=x; 
    } 
    sort(a+2,a+1+n,cmp);           //按照大于sqrt(500)的质因数排序 
    f[0][0]=1;
    ll ans=0;
    for (int i=2;i<=n;i++) 
    {
        //转移比较小的数或者是新的一个集合 
        if (i==2||a[i].prime==1||a[i].prime!=a[i-1].prime)
            for (int j=0;j<(1<<8);j++)
                for (int k=0;k<(1<<8);k++)
                    g[j][k][0]=g[j][k][1]=f[j][k];

        for (int j=(1<<8)-1;j>=0;j--)
            for (int k=(1<<8)-1;k>=0;k--) {
                if (!(j&a[i].S))    //放入k 
                    (g[j][k|a[i].S][1]+=g[j][k][1])%=p;
                if (!(k&a[i].S))    //放入j 
                    (g[j|a[i].S][k][0]+=g[j][k][0])%=p;
            }

        //只有转移新的一个集合 才需要复制到f数组  
        if (i==n||a[i].prime==1||a[i].prime!=a[i+1].prime)
            for (int j=0;j<(1<<8);j++)
                for (int k=0;k<(1<<8);k++)
                    f[j][k]=(g[j][k][0]+g[j][k][1]-f[j][k])%p;
    }
    for (int i=0;i<(1<<8);i++)
        for (int j=0;j<(1<<8);j++)
            if (!(i&j)) ans+=f[i][j];
    printf("%lld\n",(ans%p+p)%p);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值