Codeforces 720F - Array Covering(wqs 二分+线段树优化 dp)
出题人不讲武德!它卡常还卡 long long!
不过 u1s1 这题质量还是不错的。
首先泛读题面,一眼猜一个凸性。噫,好,wqs 二分逃不掉了。我们二分一个斜率 \(K\),现在选择一个区间 \([l,r]\) 的代价为 \(cst(l,r)=-K+\sum\limits_{i=l}^ra_i\),可以选任意不重复的区间,最大化总代价。这样个数就不受限制了,我们现在尝试解决这个新问题。
然后我便想错了方向,我以为是什么奇怪的堆 + 线段树维护最大子段和……完全偏掉了。虽然似乎也是可以做的但是我没想出来。
正解是:考虑“每个点都要被覆盖”的条件,我们扫描线并在右端点处加入这些区间的贡献,发现一个时刻只有最左端没被覆盖的点是有用的,然后设计二维 DP 状态并用线段树优化之。
具体来说设 \(f_{i,j}\) 表示目前加入了右端点 \(\le i\) 的区间,最左端没被覆盖的点为 \(j\) 总贡献的最大值,再设 \(g_i\) 表示目前加入了右端点 \(\le i\) 的区间,\(\le i\) 的点都被覆盖的情况下总贡献的最大值,考虑转移,当我们加入右端点 \(=i\) 的区间时,对于那些 \(cst(j,i)\ge 0\) 的区间,我们肯定会贪心地将它们全加入,我们对于每个 \(i\) 处理出这样的 \(j\) 的个数 \(c\),这些区间 \(cst(j,i)\) 之和 \(s\) 以及这样的 \(j\) 的最小值 \(\text{lim}\),那么分 \(f\) 和 \(g\) 的转移考虑:
- \(f_{i-1}\to f_{i}\),显然对于 \(j\ge\text{lim}\),\(f_{i,j}\) 肯定不是最优选择,我们也不必管它,而对于 \(j<\text{lim}\),如果我们不覆盖 \(j\),那么它只能转移到 \(f_{i,j}\),且 \(f_{i,j}=f_{i-1,j}+s\),直接以 \(j\) 为下标建立线段树然后在上面进行区间加即可。
- \(g_{i-1}\to f_i\),显然只有当 \(c=0\) 时 \(g_{i-1}\) 会转移到 \(f_{i,i}\),否则 \(g_{i-1}\) 转移到 \(f_i\) 都是不优的。
- \(g_{i-1}\to g_i\),显然如果 \(c\ne 0\) 那么 \(g_{i-1}+s\to g_i\)。
- \(f_{i-1}\to g_i\),这部分就比较复杂了,还是对转移到 \(g_i\) 的 \(f_{i-1,j}\) 的 \(j\) 分 \(j\ge \text{lim}\) 和 \(j<\text{lim}\) 讨论:
- 如果 \(j\ge\text{lim}\),那么直接 \(f_{i-1,j}+s\to g_i\),线段树上查个区间 \(\max\) 即可。
- 如果 \(j<\text{lim}\),那么我们肯定除了选择 \(cst(j,i)\ge 0\) 的区间,还要额外选择一个区间 \([j',i]\) 满足 \(j'\le j\) 且 \(cst(j',i)\) 最大,这等价于对前缀和数组做前缀 \(\min\) 得到数组 \(mn\_pre\),那么 \(cst(j',i)\) 的最大值就是 \(sum_i-mn\_pre_{j'-1}-K\),线段树上额外维护 \(f_{i-1,j}-mn\_pre_{j'-1}\) 的最大值即可。
时间复杂度 \(n\log^2n\)。
注意 wqs 二分的细节,根据 wqs 那一套理论,我们在最大化总代价的前提下也要最大化区间个数,这点可以用一个 pair 存储 DP 状态,代价为第一关键字,区间个数为第二关键字。具体见代码,感觉写得挺清楚的。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mp make_pair
typedef __int128_t ll;
typedef pair<ll, ll> pll;
template<typename T1, typename T2> void chkmax(T1 &x, T2 y) {if (x < y) x = y;}
template<typename T1, typename T2> void chkmin(T1 &x, T2 y) {if (x > y) x = y;}
const int MAXN = 1e5;
const ll INF = 1e18;
int n, a[MAXN + 5]; ll k;
int ord[MAXN + 5], min_ord[MAXN + 5], rk[MAXN + 5];
ll sum[MAXN + 5], premin[MAXN + 5], sum_sorted[MAXN + 5];
void init() {
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i], sum_sorted[i] = sum[i];
for (int i = 0; i <= n; i++) ord[i] = i;
sort(ord, ord + n + 1, [&](int x, int y) {return mp(sum[x], x) < mp(sum[y], y);});
for (int i = 0; i <= n; i++) rk[ord[i]] = i;
for (int i = 1; i <= n; i++) premin[i] = min(premin[i - 1], sum[i]);
sort(sum_sorted, sum_sorted + n + 1);
min_ord[0] = ord[0];
for (int i = 1; i <= n; i++) min_ord[i] = min(min_ord[i - 1], ord[i]);
}
pll operator + (pll X, pll Y) {return mp(X.fi + Y.fi, X.se + Y.se);}
struct fenwick {
ll t[MAXN + 15];
void clear() {memset(t, 0, sizeof(t));}
fenwick() {clear();}
void add(int x, ll v) {x += 5; for (int i = x; i <= n + 10; i += (i & (-i))) t[i] += v;}
ll query(int x) {x += 5; ll ret = 0; for (int i = x; i; i &= (i - 1)) ret += t[i]; return ret;}
} T1, T2;
struct segtree {
struct node {int l, r; pll mx_dp, mx_dp_sub_mn, lz;} s[MAXN * 4 + 5];
void pushup(int k) {
s[k].mx_dp = max(s[k << 1].mx_dp, s[k << 1 | 1].mx_dp);
s[k].mx_dp_sub_mn = max(s[k << 1].mx_dp_sub_mn, s[k << 1 | 1].mx_dp_sub_mn);
}
void build(int k, int l, int r) {
s[k].l = l; s[k].r = r; s[k].mx_dp = s[k].mx_dp_sub_mn = mp(-INF, 0); s[k].lz = mp(0, 0);
if (l == r) return; int mid = l + r >> 1; build(k << 1, l, mid); build(k << 1 | 1, mid + 1, r);
}
void tag(int k, pll v) {s[k].lz = s[k].lz + v; s[k].mx_dp = s[k].mx_dp + v; s[k].mx_dp_sub_mn = s[k].mx_dp_sub_mn + v;}
void pushdown(int k) {if (s[k].lz.fi || s[k].lz.se) tag(k << 1, s[k].lz), tag(k << 1 | 1, s[k].lz), s[k].lz = mp(0, 0);}
void upd(int k, int p, pll v) {
if (s[k].l == s[k].r) {
s[k].mx_dp = s[k].mx_dp_sub_mn = v;
if (p) s[k].mx_dp_sub_mn.fi -= premin[p - 1];
return;
}
pushdown(k);
int mid = s[k].l + s[k].r >> 1;
if (p <= mid) upd(k << 1, p, v); else upd(k << 1 | 1, p, v);
pushup(k);
}
void add(int k, int l, int r, pll v) {
if (l > r) return; if (l <= s[k].l && s[k].r <= r) return tag(k, v), void();
pushdown(k); int mid = s[k].l + s[k].r >> 1;
if (r <= mid) add(k << 1, l, r, v);
else if (l > mid) add(k << 1 | 1, l, r, v);
else add(k << 1, l, mid, v), add(k << 1 | 1, mid + 1, r, v);
pushup(k);
}
pll query_dp(int k, int l, int r) {
if (l > r) return mp(-INF, 0);
if (l <= s[k].l && s[k].r <= r) return s[k].mx_dp;
pushdown(k); int mid = s[k].l + s[k].r >> 1;
if (r <= mid) return query_dp(k << 1, l, r);
else if (l > mid) return query_dp(k << 1 | 1, l, r);
else return max(query_dp(k << 1, l, mid), query_dp(k << 1 | 1, mid + 1, r));
}
pll query_dp_sub(int k, int l, int r) {
if (l > r) return mp(-INF, 0);
if (l <= s[k].l && s[k].r <= r) return s[k].mx_dp_sub_mn;
pushdown(k); int mid = s[k].l + s[k].r >> 1;
if (r <= mid) return query_dp_sub(k << 1, l, r);
else if (l > mid) return query_dp_sub(k << 1 | 1, l, r);
else return max(query_dp_sub(k << 1, l, mid), query_dp_sub(k << 1 | 1, mid + 1, r));
}
} T;
pll calc(ll K) {
T1.clear(); T2.clear(); T.build(1, 0, n); pll dp = mp(0, 0); T.upd(1, 0, dp);
for (int i = 1; i <= n; i++) {
T1.add(rk[i - 1], 1); T2.add(rk[i - 1], sum[i - 1]);
int pos = upper_bound(sum_sorted, sum_sorted + n + 1, sum[i] - K) - sum_sorted - 1;
ll c = T1.query(pos), s = T2.query(pos); s = c * (sum[i] - K) - s;
dp = dp + mp(s, c); T.add(1, 0, n, mp(s, c));
int lim = (!c) ? i : min_ord[pos]; if (lim == 0 && sum[i] >= K) --lim;
pll tmp = T.query_dp(1, lim + 1, i); chkmax(dp, tmp);
if (!c) T.upd(1, i, dp), dp = mp(-INF, 0);
tmp = T.query_dp_sub(1, 0, lim); tmp = tmp + mp(sum[i] - K, 1);
chkmax(dp, tmp);
}
return dp;
}
int main() {
scanf("%d%lld", &n, &k);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
init();
ll L = -5e9, R = 5e9, p = 0;
while (L <= R) {
ll mid = L + R >> 1;
if (calc(mid).se >= k) p = mid, L = mid + 1;
else R = mid - 1;
}
printf("%lld\n", (long long)(calc(p).fi + k * p));
return 0;
}

浙公网安备 33010602011771号