51Nod 1597 有限背包计数问题

首先这是一个多重背包

但是它的数据非常特殊,我们可以利用其性质优化算法

一个显然的优化是
\(i>\sqrt n\)时,可以取消个数限制
\(f[i][j]\)表示选了\(i\)个物品,体积为\(j\)的方案数
一共有两种转移
可以由\(i-1\)个物品加上一个最小的物品\(\sqrt n+1\)
可以由\(i\)个物品全部加一(全部换成下一个)
\(f[i][j]=f[i-1][j-(\sqrt n+1)]+f[i][j-i]\)
考虑得到一个答案的过程
对于每一个过程都会得到一个合法的答案
对于每一个合法的答案都有唯一一个过程

因为对于每一个方案
如果其中存在最小的物品
那么一定是由第一种方案转移
否则一定是由第二种方案转移

比如\(n=16\)

体积为\(16\)的一种方案是\(5+5+6\)
这个状态一定由\(5+6\)转移来
因为\(4=\sqrt 16\),比最小的物品还小
所以\(4+4+5\)是不可能的

体积为\(12\)的一种方案是\(6+6\)
这个状态一定由\(5+5\)转移来
因为方案里并没有\(5\)(最小的物品)

所以方案与过程一一对应
复杂度\(O(n\sqrt n)\)

剩下的是多重背包
\(f[i][j]=\sum_{k=0}^{i}{f[i-1][j-i*k]}\)
按模\(i\)分类
复杂度\(O(n\sqrt n)\)

最后枚举多少空间给前\(\sqrt n\)个物品(剩下空间给其它物品)
统计答案即可
复杂度\(O(n\sqrt n)\)

#include<bits/stdc++.h>

using namespace std;

#define gc c=getchar()
#define r(x) read(x)

template<typename T>
inline void read(T&x){
    x=0;T k=1;char gc;
    while(!isdigit(c)){if(c=='-')k=-1;gc;}
    while(isdigit(c)){x=x*10+c-'0';gc;}x*=k;
}

const int p=23333333;
const int N=1e5+7;

int sum[N],f1[2][N],f2[2][N];

inline int add(int a,int b){
    a+=b;
    if(a>=p)a-=p;
    return a;
}

int main(){
    int n;r(n);
    int t=sqrt(n);
    
    f1[0][0]=1;
    for(int i=1;i<=t;++i){
        for(int j=0;j<=n;++j){
            sum[j%i]=add(sum[j%i],f1[i&1^1][j]);
            if(j-i*(i+1)>=j%i)sum[j%i]=add(sum[j%i],p-f1[i&1^1][j-i*(i+1)]);
            f1[i&1][j]=sum[j%i];
        }
        memset(sum,0,i<<2);
    }
    
    memset(sum,0,(n+1)<<2);
    f2[0][0]=1;
    for(int i=1;i<=t;++i){
        memset(f2[i&1]+(i-1)*(t+1),0,(t+1)<<2);
        for(int j=i*(t+1);j<=n;++j){
            f2[i&1][j]=add(f2[i&1][j-i],f2[i&1^1][j-(t+1)]);
            sum[j]=add(sum[j],f2[i&1][j]);
        }
    }
    
    sum[0]=1;
    long long ans=0;
    for(int i=0;i<=n;++i)(ans+=1ll*f1[t&1][i]*sum[n-i])%=p;
    
    printf("%lld\n",ans);
}

转载于:https://www.cnblogs.com/yicongli/p/9788158.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值