[51nod1597] 有限背包计数问题

题目描述

你有一个大小为n的背包,你有n种物品,第i种物品的大小为i,且有i个,求装满这个背包的方案数有多少
两种方案不同当且仅当存在至少一个数i满足第i种物品使用的数量不同
1<=n<=10^5
你需要将答案对23333333取模

分析

我们发现后面的物品只能取很少,考虑一下根号算法。
对于前√n种,可以直接做多重背包计数DP。

多重背包计数DP

计数可不能二进制拆分···
设f[i][j]表示做到第i种物品凑出j容量的方案数,那么
f[i][j]=f[i1][j]+f[i][jv[i]]f[i1][j(cnt[i]+1)v[i]]
这其实是个小容斥嘛,第一项明显是这种物品不选,第二项则是在之前选了若干个i这种物品之后又选一个,第三项则是把到目前为止刚好选爆了数量限制的方案减去。

对于后面的√n+1~n的物品,所有物品最多取√n个,我们考虑设g[i][j]表示选了i个时容量为j的方案。然后可以这样转移, g[i][j]=g[i1][jn1]+g[i][ji] ,表示我可以多选一个√n+1,也可以让当前选出来的那几个全部加上1,这样可以保证我们把所有的组合方式都选出来,因为是转移的所有序列都是有序的。
然后合并一下这两个数组就好了。注意g[i][j]是选了i个时的情况,不是选了1~i这么多个的情况。

代码

#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=100005,mo=23333333;
int f[2][N],a[N],g[2][N],h[N],i,j,n,lim,k,cnt,l,q1,q2,ans,w1;
int main()
{
    scanf("%d",&n);
    lim=trunc(sqrt(n));
    cnt=n/(lim+1);
    f[0][0]=1;
    q1=0;
    q2=1;
    fo(i,1,lim)
    {
        fo(j,0,n) f[q2][j]=0;
        fo(j,0,n)
        {
            f[q2][j]=f[q1][j];
            if (j>=i) f[q2][j]=(f[q2][j]+f[q2][j-i])%mo;
            if (j>=(i+1)*i) f[q2][j]=(f[q2][j]-f[q1][j-(i+1)*i]+mo)%mo;
        }
        q1^=1;
        q2^=1;
    }
    w1=q1;
    q1=0;
    q2=1;
    h[0]=g[0][0]=1;
    fo(i,1,cnt)
    {
        fo(j,0,n) g[q2][j]=0;
        fo(j,lim+1,n) g[q2][j]=(g[q1][j-lim-1]+g[q2][j-i])%mo;
        fo(j,0,n) h[j]=(h[j]+g[q2][j])%mo;
        q1^=1;
        q2^=1;
    }
    ans=0;
    fo(i,0,n)
        ans=((ll)ans+(ll)h[i]*f[w1][n-i]%mo)%mo;
    printf("%d",ans);
}

你可能感兴趣的:(动态规划,暴力,根号算法等)