缺一分治
顾名思义,就是要处理的问题里有一个地方是和别的的计算不同的,所以我们可以把它到最后单独算,然后再和别的合起来。
以题为例。
因为是不降的,所以最多会有一个数组没选满,很好证明。
- 如果有两个数组没有选满,那么放弃一个数组里的一些数而补满另一个数组,这样显然不劣。
我们可以想到一个 \(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;
}

浙公网安备 33010602011771号