AtCoder ABC159F - Knapsack for All Segments 题解 背包计数DP
题目链接:https://atcoder.jp/contests/abc159/tasks/abc159_f
解题思路:
枚举每个区间 \([i, j]\),在每个区间都进行背包计数。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const long long mod = 998244353;
const int maxn = 3005;
int n, S, a[maxn];
long long f[maxn], ans;
int main() {
cin >> n >> S;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) {
fill(f, f+S+1, 0);
f[0] = 1;
for (int j = i; j <= n; j++) {
for (int k = S; k >= a[j]; k--)
f[k] = (f[k] + f[k-a[j]]) % mod;
// ans = (ans + f[S] * i % mod * (n - j + 1) % mod) % mod;
ans = (ans + f[S]) % mod;
}
}
cout << ans << endl;
return 0;
}
很明显,由于时间复杂度是 \(O(n^2 s)\),会超时。
考虑到一个区间 \([i,j]\) 造成的贡献是 \(i \times (n - j + 1)\)。
然后我们这里 \(f_i\) 的含义要发生变化,
\(f_i\) 表示目前所有子序列和为 \(i\) 的子序列的左端点的下标 总和。
然后枚举子序列右端点下标(用 \(j\) 表示,虽然示例代码里面用的是 \(i\),但是这里用 \(j\)),在 \(a_j\) 必选的情况下,
所有满足以 \(a_i\) 开头以 \(a_j\) 结尾且子序列和为 \(S\) 的子序列对应的 \(\sum i\) 为 \(f_{S - a_j}\),
则总的答案将加上 \((\sum i) \times (n - j + 1)\),即 \(f_{S - a_j} \times (n - j + 1)\)。
这样处理之后,算法的时间复杂度降为 \(O(nS)\),可以通过本题。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const long long mod = 998244353;
const int maxn = 3005;
int n, S, a[maxn];
long long f[maxn], ans;
int main() {
cin >> n >> S;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) {
if (a[i] < S) {
ans = (ans + f[ S-a[i] ] * (n - i + 1)) % mod;
}
if (a[i] == S)
ans = (ans + 1ll * i * (n - i + 1)) % mod;
for (int j = S; j >= a[i]; j--) {
f[j] = (f[j] + f[ j-a[i] ]) % mod;
}
f[a[i]] = (f[a[i]] + i) % mod;
}
cout << ans << endl;
return 0;
}
这里特别要注意的是 \(f_i\) 的含义。