题目大意
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[x2n−bi]F(x)
。
对于第
i
种物品,其生成函数为
即
1−x(ai+1)i1−xi
。
则
F(x)=∏ni=1(1−x(ai+1)i)∏ni=111−xi
我们分开两部分考虑。
第一部分
注意到
ai>=0
,
ai<ai+1
,这意味着
ai>=i−1
。
那么
(ai+1)i>=i2
。
因为我们的多项式要在模
x2n+1
意义下计算,因此可以发现只有前根号个有用。
不妨先算出其余部分,最后把这根号个单项乘上去,那么复杂度为
O(n√n)
。
第二部分
我们希望计算
∏ni=111−xi
模
x2n+1
。
我们探讨发现这个式子的意义其实是把一个数拆分成一些<=n的正整数之和的本质不同方案数。
这样不好做,我们进行一些转化。以下均默认在模
x2n+1
意义下进行。
∏ni=111−xi
=∏2ni=111−xi∏2ni=n+1(1−xi)
=∏2ni=111−xi(1−∑2ni=n+1xi)
设
P(x)=∑ni=111−xi
模
xn
。
则前面部分就是
P(x)
。后面部分很容易处理。
现在的意义是把一个数拆分成任意一些正整数之和的本质不同方案数,即拆分数的计算。
拆分数
计算拆分数有一个经典做法。
即五边形数定理。
p(n)=∑k>=1(−1)k+1[p(n−k(3k+1)2)+p(n−k(3k−1)2)]
证明请研究http://blog.csdn.net/visit_world/article/details/52734860
根据该式子可以在
O(n√n)
的时间内预处理拆分数。
#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);
}
}