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;
}
既然选择了远方,便只顾风雨兼程。