CF671E Organizing a Race 题解
题意
- 有 个点排成一行,第 个点与第 个点之间的距离为 个单位。
- 每个点都有一个加油站,第 个点的加油站可以给你的车加能跑 个单位的油。
- 若一辆初始没有油的车能从 一路向右开到 ,也能从 一路向左开到 ,则称 之间可以往返。
- 另外,你有 次将某个 加 的机会。
- 请你求出在 之间可以往返的情况下, 的最大值。
- ,。
题解
考虑什么条件下 之间可以往返。
显然为: 以及 考虑对 的前缀和 ,则条件变为: 以及 设 ,,则条件变为简单的两个等式: 若 加 ,则对于 , 加 ;对于 , 减 ;对于 , 加 。
先考虑 。
从 到 枚举 ,用单调栈维护一个单调上升的子序列 ,其中 。则 一定是贪心地依次对 使用,每次将 加 。
再考虑 。
到某个 后,设剩下的 为 ,上一步使用完后的 为 ,则要满足 ,即 。
因此,我们可以先在单调栈上二分出只考虑 的情况下的最大 ,即找到满足 的最大 ,则最大 。
此时,最优解的范围一定在 中,再在这个范围内的线段树上二分找到最大的 ,然后取每次 的最大值即可。
具体来说,我们用线段树维护 ,每次往单调栈里加一个位置 后,将单调栈中前一个位置 的贡献 加到 上,退栈时相应减去。另外,对于 左边的部分,为了方便可以将 设为 。
线段树上二分时,对于一个节点 ,如果 ,则意味着 的 处一定是一个合法解,因此递归往右儿子找,否则递归往左儿子找。
为什么 就意味着 的 处一定是一个合法解呢?合法条件为 ,那么 中有没有可能比 大呢?
其实很显然,注意到 是 加上 的一个数生成的就可以了。
时间复杂度 。
代码
const int N = 1e5 + 7;
const ll inf = 1e18;
int n, ans, s[N], p;
ll k, w[N], g[N], a[N], b[N], c[N], d[N];
struct T {
int l, r;
ll x, z, d;
} t[N<<2];
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r, t[p].x = -inf;
if (t[p].l == t[p].r) return;
build(ls, l, md), build(rs, md + 1, r);
}
inline void upd(int p) {
t[p].x = max(t[ls].x, t[rs].x);
t[p].d = max(t[ls].d, t[rs].d);
}
inline void spd(int p) {
if (t[p].z) {
t[ls].x += t[p].z;
t[ls].z += t[p].z;
t[rs].x += t[p].z;
t[rs].z += t[p].z;
t[p].z = 0;
}
}
void setx(int p, int x, ll k) {
if (t[p].l == t[p].r)
return t[p].x = t[p].d = k, void();
spd(p);
if (x <= md) setx(ls, x, k);
else setx(rs, x, k);
upd(p);
}
void add(int p, int l, int r, ll k) {
if (t[p].l >= l && t[p].r <= r)
return t[p].x += k, t[p].z += k, void();
spd(p);
if (l <= md) add(ls, l, r, k);
if (r > md) add(rs, l, r, k);
upd(p);
}
int ask(int p, int l, int r, ll x) {
if (t[p].l > r || t[p].d + k < x) return 0;
if (t[p].l == t[p].r) return t[p].l;
spd(p);
if (md < l) return ask(rs, l, r, x);
if (r <= md) return ask(ls, l, r, x);
if (t[rs].d + k >= max(x, t[ls].x)) {
int ret = ask(rs, l, r, max(x, t[ls].x));
if (ret) return ret;
}
return ask(ls, l, r, x);
}
int main() {
rd(n), rd(k), rda(w, n - 1), rda(g, n);
for (int i = 1; i < n; i++) a[i] = a[i-1] + w[i];
for (int i = 1; i <= n; i++) b[i] = b[i-1] + g[i];
for (int i = 1; i <= n; i++) c[i] = a[i-1] - b[i-1];
for (int i = 1; i <= n; i++) d[i] = b[i] - a[i-1];
build(1, 1, n);
for (int i = n; i; i--) {
while (p && c[i] >= c[s[p]]) {
if (p > 1)
add(1, s[p-1] - 1, n, c[s[p]] - c[s[p-1]]);
--p;
}
s[++p] = i, setx(1, i, d[i]);
if (p > 1) add(1, s[p-1] - 1, n, c[s[p-1]] - c[i]);
int l = 1, r = p;
while (l < r) {
int mid = (l + r) >> 1;
if (c[s[mid]] - c[i] <= k) r = mid;
else l = mid + 1;
}
r = r == 1 ? n : s[r-1] - 1;
ans = max(ans, ask(1, i, r, -inf) - i + 1);
}
print(ans);
return 0;
}
评论
暂无已公开评论。