CF573E Bear and Bowling 题解
题意
- 给定一个长度为 的序列 。
- 你要求一个 的子序列 (可以为空),使得 的值最大。
- ,。
题解
首先有个显然的贪心:每次选择贡献最大的位置,直到贡献非正,此时贡献和即为答案。
这里的贡献可以简单的写成 ,其中 为 之前被选择的位置数 , 表示 之后被选择的位置的 之和。
相当于我们要维护 个一次函数 ,支持区间 加和全局最大值。
这玩意儿是有办法 维护的(详见 EI 的提交),但非常复杂。
考虑分块,块内使用单调队列维护一个上凸壳。
对于每次最大的位置 ,先将 的贡献计入答案,然后删除 (将 的贡献设为 )。
对于 之前的位置 ,相当于 加上 ;对于 之后的位置 ,相当于 加上 。
对于每一整块,打上两个分别关于 的标记即可;而对于 所在的块,暴力重构即可。
总时间复杂度 。
代码
const int N = 1e5 + 7, M = 370;
const ld inf = 1e18L;
int n, m, k, a[N], b[N], p[N], l[M], r[M];
ll f[N], ans;
struct T {
int l, r, q[M];
ll k, b;
inline ll c(int i) {
return f[i] + k * a[i] + b;
}
inline ld s(int i, int j) {
if (a[i] == a[j]) return f[i] > f[j] ? -inf : inf;
return 1.0L * (f[i] - f[j]) / (a[i] - a[j]);
}
inline void build(int x) {
for (int i = ::l[x]; i <= ::r[x]; i++) f[i] += k * a[i] + b;
k = b = l = r = 0;
for (int i = ::l[x]; i <= ::r[x]; i++) {
while (r - l > 1 && s(q[r-2], q[r-1]) < s(q[r-1], p[i])) --r;
q[r++] = p[i];
}
}
inline pair<ll, int> ask() {
while (r - l > 1 && c(q[l]) <= c(q[l+1])) ++l;
return mp(c(q[l]), q[l]);
}
} t[M];
int main() {
rd(n), m = sqrt(n);
for (int i = k = 1; i <= n; k += !(i % m), i++)
rd(a[i]), f[i] = a[i], p[i] = i, b[i] = k;
if (!(n % m)) --k;
for (int i = 1; i <= k; i++) {
l[i] = r[i-1] + 1, r[i] = min(i * m, n);
sort(p + l[i], p + r[i] + 1, [&](int i, int j) { return a[i] < a[j]; });
t[i].build(i);
}
while (1) {
pair<ll, int> o = mp(0ll, 0);
for (int i = 1; i <= k; i++) o = max(o, t[i].ask());
if (!o.fi) return print(ans), 0;
ans += o.fi, f[o.se] = -inf;
for (int i = 1; i < b[o.se]; i++) t[i].b += a[o.se];
for (int i = l[b[o.se]]; i < o.se; i++) f[i] += a[o.se];
for (int i = o.se + 1; i <= r[b[o.se]]; i++) f[i] += a[i];
for (int i = b[o.se] + 1; i <= k; i++) ++t[i].k;
t[b[o.se]].build(b[o.se]);
}
return 0;
}
评论
暂无已公开评论。