loj6089 小Y的背包计数问题

Description

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

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

求小 ( ext{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
", ans);
  return 0;
}
原文地址:https://www.cnblogs.com/newbielyx/p/12181312.html