缺一分治

顾名思义,就是要处理的问题里有一个地方是和别的的计算不同的,所以我们可以把它到最后单独算,然后再和别的合起来。

以题为例。

因为是不降的,所以最多会有一个数组没选满,很好证明。

  • 如果有两个数组没有选满,那么放弃一个数组里的一些数而补满另一个数组,这样显然不劣。

我们可以想到一个 \(dp\),具体来说就是枚举一个位置是没选满的,然后别的位置就是做背包,复杂度是 \(O(n^2k)\) 的。

考虑优化,这个时候就要用到缺一分治了。

具体地,我们做分治的过程,每次分治到区间 \(l,r\),意思就是那个不选满的数组在区间 \(l,r\) 中,每次就分别对于这个区间的左/右进行01背包,然后递归处理另一边。

考虑什么时候算答案,显然就是 \(l=r\) 的时候。这个时候直接暴力和别的情况合并就行了

放一下代码

#include <iostream>
#define int long long

using namespace std;
const int N = 3010;

int n, k, ans;
int t[N], f[N];
int a[N][N], tmp[N][N];

void dfs (int l, int r, int d) {
    if (l == r) {
        for (int i = 0; i <= min (k, t[l]); ++i)
            ans = max (ans, f[k - i] + a[l][i]);
        return;
    } int mid = (l + r) >> 1;
    for (int i = 0; i <= k; ++i)
        tmp[d][i] = f[i];
    for (int i = mid + 1; i <= r; ++i)
        for (int j = k; j >= 0; --j) if (j >= t[i])
            f[j] = max (f[j], f[j - t[i]] + a[i][t[i]]);
    dfs (l, mid, d + 1);
    for (int i = 0; i <= k; ++i)
        f[i] = tmp[d][i];
    for (int i = l; i <= mid; ++i)
        for (int j = k; j >= 0; --j) if (j >= t[i])
            f[j] = max (f[j], f[j - t[i]] + a[i][t[i]]);
    dfs (mid + 1, r, d + 1);
}

int read () {
    int X = 0, qwe = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-') qwe = -1;
        ch = getchar();
    }
    while (isdigit(ch))
        X = X * 10 + (int)(ch - '0'),
        ch = getchar();
    return X * qwe;
}

signed main () {
    n = read(), k = read();
    for (int i = 1, x; i <= n; ++i) {
        t[i] = read();
        for (int j = 1; j <= t[i]; ++j)
            if (j <= k) a[i][j] = read(), a[i][j] += a[i][j - 1];
            else x = read();
        t[i] = min (t[i], k);
    }
    dfs (1, n, 1);
    cout << ans << endl;
    return 0;
}
posted @ 2025-05-15 20:29  Rose_Lu  阅读(115)  评论(0)    收藏  举报