loj6089 小Y的背包计数问题 - dp、计数、背包

Description

\(\text{Y}\) 有一个大小为 \(n\) 的背包,并且小 \(\text{Y}\)\(n\) 种物品。

对于第 \(i\) 种物品,共有 \(i\) 个可以使用,并且对于每一个 \(i\) 物品,体积均为 \(i\)

求小 \(\text{Y}\) 把该背包装满的方案数为多少,答案对于 \(23333333\) 取模。

定义两种不同的方案为:当且仅当至少存在一种物品的使用数量不同。

Solution

考虑将物品分为两类,一类是小于等于 \(\sqrt{N}\) 的物品,另一类式大于 \(\sqrt{N}\) 的物品。

小于等于 \(\sqrt{N}\) 的物品,直接跑一个多重背包,前缀和优化即可。

大于 \(\sqrt{N}\) 的物品,由于每种物品不可能放入超过 \(\sqrt{N}\) 个,因此可以视为无限个物品,跑一遍完全背包。

但是直接跑完全背包是 \(O(N^2)\) 的,转移时有一个技巧,由于这里的物品体积都是连续的,所以转移可以分两种,一是给全部物品的体积加 \(1\),二是插入一个 \(\sqrt{N}+1\) 体积的物品,可以保证,这样一定能够构造出所有的方案。

Code

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int _ = 1e5 + 10;
const int __ = 320 + 5;
const ll mod = 23333333;
int N, M;
ll f[_], g[__][_], sum[_], ans;

int main() {
#ifndef ONLINE_JUDGE
	freopen("backpack.in", "r", stdin);
	freopen("backpack.out", "w", stdout);
#endif
	scanf("%d", &N);
	M = sqrt(N);
	f[0] = 1;
	for (int i = 1; i <= M; ++i) {
		for (int j = 0; j < i; ++j) {
			int now = 0;
			for (int k = j; k <= N; k += i) sum[++now] = f[k];
			for (int k = 1; k <= now; ++k) sum[k] = (sum[k] + sum[k - 1]) % mod;
			for (int k = j, p = 1; k <= N; k += i, ++p)
				f[k] = (sum[p] - sum[max(0, p - i - 1)] + mod) % mod;
		}
	}
	memset(sum, 0, sizeof(sum));
	g[0][0] = 1;
	sum[0] = 1;
	for (int i = 1; i <= M; ++i) {
		for (int j = min(i, M + 1); j <= N; ++j) {
			if (j >= i) g[i][j] = (g[i][j] + g[i][j - i]) % mod;
			if (j >= M + 1) g[i][j] = (g[i][j] + g[i - 1][j - M - 1]) % mod;
			sum[j] = (sum[j] + g[i][j]) % mod;
		}
	}
	for (int i = 0; i <= N; ++i) ans = (ans + f[i] * sum[N - i] % mod) % mod;
	printf("%lld\n", ans);
  return 0;
}
posted @ 2020-01-11 22:07  newbielyx  阅读(259)  评论(0编辑  收藏  举报