[hdu6042]Journey with Knapsack

题目大意

Rosemary有一个容积为2n的背包,还有n种物品,第i种物品的容积为i,有ai个,保证a是非负整数且递增(即 ai>=0 ai<ai+1 )。
现在lihua摆出了m个装备帮助Rosemary完成他的旅行,第i个装备的容积为bi,Rosemary必须选择恰好一个装备以及若干个物品装进背包去旅行,要求背包装满,问有多少种方案。
两种方案不同,当且仅当选择的装备不同,或者某种物品的选择数量不同。
n<=5*10^4。对10^9+7取模。

生成函数

不妨设选择物品的生成函数为 F(x)
那么 ans=mi=1[x2nbi]F(x)
对于第 i 种物品,其生成函数为1+xi+x2i++xaii
1x(ai+1)i1xi
F(x)=ni=1(1x(ai+1)i)ni=111xi
我们分开两部分考虑。

第一部分

注意到 ai>=0 ai<ai+1 ,这意味着 ai>=i1
那么 (ai+1)i>=i2
因为我们的多项式要在模 x2n+1 意义下计算,因此可以发现只有前根号个有用。
不妨先算出其余部分,最后把这根号个单项乘上去,那么复杂度为 O(nn)

第二部分

我们希望计算 ni=111xi x2n+1
我们探讨发现这个式子的意义其实是把一个数拆分成一些<=n的正整数之和的本质不同方案数。
这样不好做,我们进行一些转化。以下均默认在模 x2n+1 意义下进行。
ni=111xi
=2ni=111xi2ni=n+1(1xi)
=2ni=111xi(12ni=n+1xi)
P(x)=ni=111xi xn
则前面部分就是 P(x) 。后面部分很容易处理。
现在的意义是把一个数拆分成任意一些正整数之和的本质不同方案数,即拆分数的计算。

拆分数

计算拆分数有一个经典做法。
即五边形数定理。
p(n)=k>=1(1)k+1[p(nk(3k+1)2)+p(nk(3k1)2)]
证明请研究http://blog.csdn.net/visit_world/article/details/52734860
根据该式子可以在 O(nn) 的时间内预处理拆分数。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=50000+10,mo=1000000007;
int f[maxn*2],g[maxn*2],h[maxn*2],a[maxn];
int i,j,k,l,r,t,n,m,ans,ca;
int main(){
    while (scanf("%d%d",&n,&m)!=EOF){
        ca++;
        fo(i,1,n) scanf("%d",&a[i]);
        g[0]=1;
        fo(i,1,2*n){
            g[i]=0;
            fo(k,1,n){
                if (k%2) r=1;else r=-1;
                t=k*(3*k-1)/2;
                if (t>i) break;
                (g[i]+=g[i-t]*r)%=mo;
                t=k*(3*k+1)/2;
                if (t>i) continue;
                (g[i]+=g[i-t]*r)%=mo;
            }
        }
        fo(k,1,n){
            t=(a[k]+1)*k;
            if (t>2*n) break;
            fd(i,2*n,t) (g[i]-=g[i-t])%=mo;
        }
        fo(i,0,2*n) h[i]=0;
        fo(i,0,n-1) (h[i+n+1]-=g[i])%=mo;
        fo(i,1,2*n) (h[i]+=h[i-1])%=mo;
        fo(i,0,2*n) f[i]=(g[i]+h[i])%mo;
        ans=0;
        fo(i,1,m){
            scanf("%d",&t);
            (ans+=f[2*n-t])%=mo;
        }
        (ans+=mo)%=mo;
        printf("Case #%d: %d\n",ca,ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值