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\) 的含义。

posted @ 2025-04-29 01:43  quanjun  阅读(17)  评论(0)    收藏  举报