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;
}
posted @ 2021-08-17 23:01  mrclr  阅读(60)  评论(0编辑  收藏  举报