HDU6042 Journey with Knapsack
传送门
题面:一个体积为\(2*n\)的背包,有\(n(n\leqslant 5*10^4)\)种食物,第\(i\)种食物的体积是\(i\),数量是\(a_i(0 \leqslant a_1<a_2< \cdots <a_n \leqslant 2*n)\),还有\(m\)种装备,第\(i\)种装备的体积是\(b_i(1\leqslant b_i \leqslant 2*n)\),求装一些食物和一件装备使得背包装满的方案数。
(感谢博主HopeForBetter的翻译)
这题算是比较明显的组合问题了,如果我们能算出一定体积装食物的方案数,那枚举装备即可。
食物的方案数的生成函数很好构造:\(f(x)=\prod\limits_{i=1}^n \frac{1-x^{(a_i+1)i}}{1-x^i}\).
关键是怎么求这个式子的第\(i\)项的系数。
首先要明确的是,我么只要小于等于\(x^{2n}\)项的系数,即模\(x^{2n+1}\)的条件下这些多项式的乘积,所以以下所有运算都是在模\(x^{2n+1}\)条件下的。
我们将该式子分成两部分:\(f(x) = \prod\limits_{i=1}^n (1-x^{(a_i+1)i}) * \prod\limits_{i=1}^n \frac1{1-x^i}\).
1.求\(\prod\limits_{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.求\(\prod\limits_{i=1}^n \frac1{1-x^i}\)
这个式子是不是很熟悉?确实,他跟整数划分的生成函数非常像,只不过是用\(1 \sim n\)的数来组成小于等于\(2n\)的数的方案数,和整数划分又有些不同。
那么我们干脆补成整数划分的正规形式:\(\prod\limits_{i=1}^n \frac1{1-x^i} = \prod\limits_{i=1}^{2n} \frac1{1-x^i} * \prod\limits_{i=n+1}^{2n} (1-x^i)\).
记\(p(i)\)表示将\(i\)划分成若干个整数相加的方案数,那么上式化简为\(\sum\limits_{i=1}^{2n}p(i)x^i \prod\limits_{i=n+1}^{2n} (1-x^i)=\sum\limits_{i=1}^{2n}p(i)x^i (1-\sum\limits_{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(n\sqrt{n})\)预处理。
而上述的式子也可以\(O(n)\)相乘:乘以\(\sum\limits_{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\n", ++T, solve());
}
return 0;
}