传送门
题面:一个体积为(2*n)的背包,有(n(nleqslant 5*10^4))种食物,第(i)种食物的体积是(i),数量是(a_i(0 leqslant a_1<a_2< cdots <a_n leqslant 2*n)),还有(m)种装备,第(i)种装备的体积是(b_i(1leqslant b_i leqslant 2*n)),求装一些食物和一件装备使得背包装满的方案数。
(感谢博主HopeForBetter的翻译)
这题算是比较明显的组合问题了,如果我们能算出一定体积装食物的方案数,那枚举装备即可。
食物的方案数的生成函数很好构造:(f(x)=prodlimits_{i=1}^n frac{1-x^{(a_i+1)i}}{1-x^i}).
关键是怎么求这个式子的第(i)项的系数。
首先要明确的是,我么只要小于等于(x^{2n})项的系数,即模(x^{2n+1})的条件下这些多项式的乘积,所以以下所有运算都是在模(x^{2n+1})条件下的。
我们将该式子分成两部分:(f(x) = prodlimits_{i=1}^n (1-x^{(a_i+1)i}) * prodlimits_{i=1}^n frac1{1-x^i}).
1.求(prodlimits_{i=1}^n (1-x^{(a_i+1)i}))
注意到有(0 leqslant a_1 < a_2 < cdots < a_n leqslant 2n),因此(a_i+1 geqslant i),所以((a_i+1)i geqslant i^2),那么最多只有(sqrt{n})项有用,因此可以先求后面部分的系数,然后暴力和这(O(sqrt{n}))个(1-x^{(a_i+1)i})相乘。
2.求(prodlimits_{i=1}^n frac1{1-x^i})
这个式子是不是很熟悉?确实,他跟整数划分的生成函数非常像,只不过是用(1 sim n)的数来组成小于等于(2n)的数的方案数,和整数划分又有些不同。
那么我们干脆补成整数划分的正规形式:(prodlimits_{i=1}^n frac1{1-x^i} = prodlimits_{i=1}^{2n} frac1{1-x^i} * prodlimits_{i=n+1}^{2n} (1-x^i)).
记(p(i))表示将(i)划分成若干个整数相加的方案数,那么上式化简为(sumlimits_{i=1}^{2n}p(i)x^i prodlimits_{i=n+1}^{2n} (1-x^i)=sumlimits_{i=1}^{2n}p(i)x^i (1-sumlimits_{i=n+1}^{2n}x^i))
最后那一个连乘化简我想了半天,因为(i in [n+1,2n]),所以任意两个相乘必然大于(2n),那被(x^{2n+1})取模后就没了,因此只能有单独的一项。
(p(i))有公式(p(n)=sum_{k geqslant 1} (-1)^{k+1}[p(n-frac{k(3k+1)}{2})+p(n-frac{k(3k-1)}{2})]),可以(O(nsqrt{n}))预处理。
而上述的式子也可以(O(n))相乘:乘以(sumlimits_{i=n+1}^{2n}x^i)相当于分别把([n+1,2n])次项加上了([0,n-1])项的系数,([n+2,2n])项加上了([0,n-2])的系数,([n+3,2n])加上了([0,n-3])的系数……那也就是说,(x^{n+1})项要加上(x^{0})项的系数,(x^{n+2})项要加上(x^0,x^1)的系数,(x^{n+3})要加上(x^0,x^1,x^2)的系数……其本质就是一个前缀和。
综上,这道题还是挺有难度的,包括观察出整数划分,以及(O(n))和(sum x^i)这种多项式相乘,我都要再消化消化。
#include<bits/stdc++.h>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 5e4 + 5;
const ll mod = 1e9 + 7;
In ll read()
{
ll ans = 0;
char ch = getchar(), las = ' ';
while(!isdigit(ch)) las = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(las == '-') ans = -ans;
return ans;
}
In void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
In ll ADD(const ll& a, const ll& b) {return a + b < mod ? a + b : a + b - mod;}
int n, m, a[maxn], b[maxn << 1];
ll p[maxn << 1], f[maxn << 1];
In ll solve()
{
int N = n << 1; p[0] = 1;
for(int i = 1; i <= N; ++i) //求p(n)
{
p[i] = 0;
for(int k = 1; k <= n; ++k)
{
int t = k * (3 * k - 1) >> 1;
if(t > i) break;
p[i] = ADD(p[i], (k & 1) ? p[i - t] : mod - p[i - t]);
t = k * (3 * k + 1) >> 1;
if(t > i) continue;
p[i] = ADD(p[i], (k & 1) ? p[i - t] : mod - p[i - t]);
}
}
fill(f, f + N + 1, 0);
for(int i = 0; i < n; ++i) f[i + n + 1] = mod - p[i]; //乘以-∑x^i
for(int i = 1; i <= N; ++i) f[i] = ADD(f[i], f[i - 1]);
for(int i = 0; i <= N; ++i) f[i] = ADD(f[i], p[i]);
for(int i = 1; i <= n; ++i) //乘以第一部分
{
int t = (a[i] + 1) * i;
if(t > N) break;
for(int i = N; i >= t; --i) f[i] = ADD(f[i], mod - f[i - t]);
}
ll ans = 0;
for(int i = 1; i <= m; ++i) ans = ADD(ans, f[N - b[i]]);
return ans;
}
int main()
{
int T = 0;
while(scanf("%d%d", &n, &m) != EOF)
{
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i <= m; ++i) b[i] = read();
printf("Case #%d: %lld
", ++T, solve());
}
return 0;
}