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

来源:互联网 发布:万能搬家软件 编辑:程序博客网 时间:2024/06/05 14:59

题目描述

你有一个大小为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<cstdio>#include<algorithm>#include<cstring>#include<cmath>#include<set>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);}
0 0
原创粉丝点击