SnackOI R1-D 战争与和平 题解

· · 个人记录

闲话

这题在测试人员里面通过人数第二多。

n 较小的部分分

爆搜每个元素属于第几组,期望得分 15pts

状压,时间复杂度 O(4^n),期望得分 30pts

优化一下枚举子集,时间复杂度 O(3^n),期望得分 35pts

特殊性质

特殊性质 BC 一起看吧。

容易发现区间长度取 12 的时候是非常优秀的,贡献很大,以此进行贪心即可。期望得分 45pts

特殊性质 AD 好像没啥特殊做法。

正解

发现,我们将数组排序后,一定会选择多个连续的段。

实际上这个很好理解,因为如果我们以此分完段后,有两组交换了一个元素,那么贡献一定不优。所以排序后取连续段是最优的。

我们发现 dp 式子好像非常有趣,就是 $dp_i = \max\{dp_{j-1} + s_i - s_{j - 1} - a_i + a_j\}$,$s$ 为前缀和数组。发现这个东西可以用线段树或者 set 优化,时间复杂度 $O(n \log n)$,期望得分 $85pts$。 用单调队列优化,时间复杂度线性,期望得分 $100pts$。实际上上面的复杂度都是除了排序以外的复杂度。所以正解实际上还是 $O(n \log n)$ 的,只是常数很小而已。 代码献上。 ```cpp #include <bits/stdc++.h> using u32 = unsigned; using i64 = long long; using u64 = unsigned long long; const int MAXN = 5e6 + 10; const i64 INF = 1e18; int n, l, r; int h, t; int q[MAXN]; i64 a[MAXN]; i64 dp[MAXN]; i64 sum[MAXN]; i64 f (int x) { return dp[x] - sum[x] + a[x + 1]; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::cin >> n >> l >> r; for (int i = 1; i <= n; ++i) { std::cin >> a[i]; } std::sort(a + 1, a + n + 1); for (int i = 1; i <= n; ++i) { sum[i] = sum[i - 1] + a[i]; } h = 1, t = 0; if (1 - r <= 0 && 0 <= 1 - l) { q[++t] = 0; } for (int i = 1; i <= n; ++i) { while (h <= t && q[h] < i - r) { ++h; } if (h <= t) { int j = q[h]; dp[i] = f(j) + sum[i] - a[i]; } else { dp[i] = -INF; } int id = i - l + 1; if (id >= 0) { while (h <= t && f(q[t]) <= f(id)) { --t; } q[++t] = id; } } std::cout << dp[n]; return 0; } ```