题解 [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;
}
下次遇到这样子有很多地方贡献一样的题目也可以考虑考虑分治了。