题解 [ARC070B] No Need

分治 nb!!!
没想到分治居然会出现在这里啊。

输入 \(n\) 个数组成的集合。若它的一个非空子集和大于 \(k\) 则它是优秀的。若在所有包含 \(x\) 的非空子集中去掉 \(x\) 它和仍大于 \(k\) 则称 \(x\) 是无用的。求无用的数个数。

很容易想到将问题转化成求对于每个数是否所有数中去掉它能拼出 \([k-x, k)\) 中的数。
这用背包可以在 \(n^2k\) 的复杂度内实现。
然后我想着前后都做一遍拼出答案什么的但是未果,因为这里要拼的不是一个数,而是 \(k\) 个数,这样的预处理并不会让时间更少。

然后回到原来的做法看有什么可优化的,其实有很多的状态都是重复的,比如说我要求去掉 \(a_i\) 的答案、去掉 \(a_{i+1}\) 的答案、去掉 \(a_{i+2}\) 的……去掉 \(a_{i+x}\) 的,其实 \(j \in (x, n]\)\(a_j\) 对于这里的背包的贡献都是存在的。

那么就可以分治了,每层都开好两个数组,一个给左边用,一个给右边用,然后左边的只让它计算右边的贡献,右边的只让它计算左边的贡献,这样子递归到一个区间 dp 数组的值就是去掉那一个区间的答案。
有点 cdq 分治的味道。

代码。
#include <iostream>
const int N = 5005;
int n, k, a[N], ans, f[N][N];
bool d[N];
void solve(int l, int r, bool *d) {
    if (l == r) {
        for (int i = 0; i <= k; i++) f[l][i] = d[i];
        return;
    }
    bool g[N];
    int mid = l + (r-l) / 2;
    for (int j = 0; j <= k; j++) g[j] = d[j];
    for (int i = l; i <= mid; i++) 
        for (int j = k; j >= a[i]; j--) d[j] |= d[j-a[i]];
    for (int i = mid+1; i <= r; i++)
        for (int j = k; j >= a[i]; j--) g[j] |= g[j-a[i]];
    solve(l, mid, g), solve(mid+1, r, d);
}
int main() {
    std::cin >> n >> k;
    d[0] = 1;
    for (int i = 1; i <= n; i++) std::cin >> a[i], ans += a[i] >= k;
    solve(1, n, d);
    for (int i = 1; i <= n; i++) {
        if (a[i] >= k) continue;
        for (int j = k-a[i]; j <= k-1; j++)
            if (f[i][j]) { ans ++; break; }
    }
    std::cout << n - ans;
}

下次遇到这样子有很多地方贡献一样的题目也可以考虑考虑分治了。

posted @ 2021-08-05 16:14  Acfboy  阅读(45)  评论(0)    收藏  举报