← 全部文章

CF671E Organizing a Race 题解

本文目录(3 节)
  1. 题意
  2. 题解
  3. 代码

CF671E Organizing a Race

题意

  • 有 nn 个点排成一行,第 ii 个点与第 i+1i + 1 个点之间的距离为 wiw_i 个单位。
  • 每个点都有一个加油站,第 ii 个点的加油站可以给你的车加能跑 gig_i 个单位的油。
  • 若一辆初始没有油的车能从 ll 一路向右开到 rr,也能从 rr 一路向左开到 ll,则称 l,rl,r 之间可以往返。
  • 另外,你有 kk 次将某个 gig_i 加 11 的机会。
  • 请你求出在 l,rl,r 之间可以往返的情况下,r−l+1r-l+1 的最大值。
  • n≤105n \le 10^5,k,wi,gi≤109k,w_i,g_i \le 10^9。

题解

考虑什么条件下 l,rl,r 之间可以往返。

显然为: {wl≤glwl+wl+1≤gl+gl+1⋯∑i=lr−1wi≤∑i=lr−1gi \begin{cases}w_l \le g_l \\\\w_l + w_{l+1} \le g_l + g_{l+1} \\\\\cdots \\\\\sum_{i=l}^{r-1} w_i \le \sum_{i=l}^{r-1} g_i\end{cases} 以及 {wr−1≤grwr−1+wr−2≤gr+gr−1⋯∑i=lr−1wi≤∑i=l+1rgi \begin{cases}w_{r-1} \le g_r \\\\w_{r-1} + w_{r-2} \le g_r + g_{r-1} \\\\\cdots \\\\\sum_{i=l}^{r-1} w_i \le \sum_{i=l+1}^{r} g_i\end{cases} 考虑对 w,gw,g 的前缀和 a,ba,b,则条件变为: {al−al−1≤bl−bl−1al+1−al−1≤bl+1−bl−1⋯ar−1−al−1≤br−1−bl−1 \begin{cases}a_l-a_{l-1} \le b_l-b_{l-1} \\\\a_{l+1}-a_{l-1} \le b_{l+1}-b_{l-1} \\\\\cdots \\\\a_{r-1}-a_{l-1} \le b_{r-1}-b_{l-1}\end{cases} 以及 {ar−1−ar−2≤br−br−1ar−1−ar−3≤br−br−2⋯ar−1−al−1≤br−bl \begin{cases}a_{r-1} - a_{r-2} \le b_r - b_{r-1} \\\\a_{r-1} - a_{r-3} \le b_r - b_{r-2} \\\\\cdots \\\\a_{r-1} - a_{l-1} \le b_r - b_{l}\end{cases} 设 ci=ai−1−bi−1c_i = a_{i-1} - b_{i-1},di=bi−ai−1d_i = b_i - a_{i-1},则条件变为简单的两个等式: {cl=max⁡i=lrcidr=max⁡i=lrdi \begin{cases}c_{l} = \max_{i=l}^{r} c_i\\\\d_{r} = \max_{i=l}^{r} d_i\end{cases} 若 gig_i 加 11,则对于 j≥ij \ge i,bjb_j 加 11;对于 j>ij > i,cjc_j 减 11;对于 j≥ij \ge i,djd_j 加 11。

先考虑 l→rl \to r。

从 nn 到 11 枚举 ll,用单调栈维护一个单调上升的子序列 cx0<cx1<cx2<⋯c_{x_0} < c_{x_1} < c_{x_2} < \cdots,其中 x0=lx_0 = l。则 kk 一定是贪心地依次对 gx1−1,gx2−1,⋯g_{x_1-1},g_{x_2-1},\cdots 使用,每次将 gxi−1g_{x_i-1} 加 cxi−cxi−1c_{x_i} - c_{x_{i-1}}。

再考虑 r→lr\to l。

到某个 xpx_p 后,设剩下的 kk 为 k′k^\prime,上一步使用完后的 dd 为 d′d^\prime,则要满足 dr′+k′≥dl…r−1′d^\prime_r + k^\prime \ge d^\prime_{l \dots r-1},即 dr+k≥max⁡i=lr−1di′d_r + k \ge \max_{i=l}^{r-1} d^\prime_{i}。

因此,我们可以先在单调栈上二分出只考虑 l→rl \to r 的情况下的最大 r′r^\prime,即找到满足 cxi−cl≤kc_{x_i} - c_{l} \le k 的最大 xix_i,则最大 r′=xi+1−1r^\prime = x_{i+1} - 1。

此时,最优解的范围一定在 [l,r′][l,r^\prime] 中,再在这个范围内的线段树上二分找到最大的 rr,然后取每次 r−l+1r-l+1 的最大值即可。

具体来说,我们用线段树维护 d′d^\prime,每次往单调栈里加一个位置 xx 后,将单调栈中前一个位置 yy 的贡献 cy−cxc_{y} - c_{x} 加到 dy−1…n′d^\prime_{y-1\dots n} 上,退栈时相应减去。另外,对于 ll 左边的部分,为了方便可以将 d′d^\prime 设为 −∞-\infty。

线段树上二分时,对于一个节点 pp,如果 max⁡rsdi+k≥max⁡1…pmiddi′\max_{rs} d_i + k \ge \max_{1\dots p_{mid}} d^\prime_i,则意味着 max⁡rsdi\max_{rs} d_i 的 ii 处一定是一个合法解,因此递归往右儿子找,否则递归往左儿子找。

为什么 max⁡rsdi+k≥max⁡1…pmiddi′\max_{rs} d_i + k \ge \max_{1\dots p_{mid}} d^\prime_i 就意味着 max⁡rsdi\max_{rs} d_i 的 ii 处一定是一个合法解呢?合法条件为 dr+k≥max⁡i=lr−1di′d_r + k \ge \max_{i=l}^{r-1} d^\prime_{i},那么 dpmid+1…i−1′d^\prime_{p_{mid+1}\dots i-1} 中有没有可能比 di+kd_i + k 大呢?

其实很显然,注意到 d′d^\prime 是 dd 加上 ≤k\le k 的一个数生成的就可以了。

时间复杂度 O(nlog⁡n)\mathcal O(n \log n)。

代码

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;
}

标签:二分、前缀和/差分、单调栈、线段树、贪心

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…