CF671E Organizing a Race

题意

有一条无限长的公路,公路上依次有 \(n​\) 个城市。每经过一个城市可以加 \(g_i​\) 升的油,一升油可以行使 \(1​\) 单位距离,两个相邻城市之间的距离是 \(w_i​\)。出发时油量为所在城市的\(g_i​\)

你需要在 \(l,r\) 之间往返,即需要从 \(l\) 出发向右开到 \(r\),并且从 \(r\) 出发向左开到 \(l\)

你可以让任意个城市的加油量 \(+x(x>0)\),要求 \(\sum x\le k\)。求最多能在多少个城市之间往返,即最大的 \(r-l+1\)

数据范围\(n\le 10^5,w_i,g_i,k\le 10^9\)

solution

\(pre_i\) 表示从 \(1\) 出发走到 \(i\),至少要再加多少油。\(suf_i\) 表示从 \(i\) 出发走到 \(1\),至少要再加多少油。

\[\begin{aligned} pre_i=pre_{i-1}-g_{i-1}+w_{i-1}\\ suf_i=suf_{i-1}-g_i+w_{i-1} \end{aligned} \]

这样我们做差分就可以分别求出 \(l\to r\) 至少要再加多少油和 \(r\to l\) 至少要再加多少油。

现在我们要做的就是尽量使这两部分共用的尽量多。先满足\(l\to r\) ,所需的加油量至少是 \(cost(l,r)=\max_{l<i\le r}\{pre_i\}-pre_l\),贪心地,让 \(l\to r\) 加油的地方一定要越靠右越好。也就是,如果存在一个 \(j\) 满足 \(pre_j>pre_i\),那么最优的方案一定是在 \(g_{j-1}\) 的地方加。这个关系可以使用单调栈维护,并且会形成一个森林。

我们在森林上 DFS,二分出最靠右的一个 \(r\),满足 \(\max_{i<j\le r}\{pre_j\}-pre_i\le k\) ,这个 \(r\) 一定是某个祖先的左边的一个。

现在我们需要只满足 \(r\to l\) ,所以把剩下的部分全部加在 \(g_r\) 上即可。这一部分的代价是 \(suf'_r-\min_{l\le i\le r}\{suf'_i\}\)。这里的 \(suf'\) 指的是在上面的修改 \(g\) 的影响下,重新计算的 \(suf\)

也就是,在满足了上面二分出的 \(r\) 的条件下,只需要满足 \(cost(l,r)+suf'_r-\min_{l\le i\le r}\{suf'_i\}\le k\) 即可。注意到 \(cost(l,r)\) 造成的修改与 \(suf'_{r}\) 的关系。\(cost(l,r)\) 的修改对 \(suf\) 造成了后缀减,也就是 \(cost(l,r)\)\(+1\)\(suf_r\) 都会 \(-1\)

\[cost(l,r)+suf'_r=suf'r \]

需要满足的条件变成

\[suf_r-\min_{l\le i< r}\{suf'_i\}\le k \]

考虑线段树上二分,\(i\le l\) 的限制可以让 \([1,l-1]\) 的部分的 \(suf'\) 变成 \(+\infin\)\(i< r\) 的限制可以让 \([r,n]\) 的部分的 \(suf'\) 变成 \(-\infin\)

现在我们需要的就是维护 \(a_i-\min_{1\le j<i} b_j\) 这个东西,需要支持区间加。

线段树的一个节点维护 \(3\) 个东西

  • \(\min suf\),记为 \(mn1\)
  • \(\min suf'\),记为 \(mn2\)
  • \(\min_{mid<x\le r}\{suf_x-\min_{l\le i< x}\{suf'_i\}\}\),即考虑整个区间的 \(suf'\)\(r\) 只在右子树里取的最小代价。记为 \(val\)

\(update\) 的时候需要 \(O(\log n)\) 在子树里二分。假设当前正在考虑的区间的左侧的 \(\min suf'\)\(mn\),那么

  • 如果 \(mn2_{ls}<mn\),那么当前区间左侧的 \(mn2\) 不需要考虑了,答案在 \(val_{id}\) 和 左子树里取 \(\min\)
  • 否则,左区间的 \(\min suf'\)\(mn\) 覆盖,答案在 \(mn1_{ls}-mn\) 和右子树里取 \(\min\)

复杂度 \(O(n\log ^2n)\)

线段树上二分的时候,假设当前正在考虑的区间的左侧的 \(\min suf'\)\(mn\),那么

  • 如果 \(mn<mn2_{ls}\)\(ls\) 内的条件变为 \(suf_r-mn\le k\),直接二分即可,然后继续考虑右子树(注意每一个线段树上的节点最多会向左二分一次,复杂度也是 \(O(n\log^2n)\)
  • 否则与 \(mn\) 无关,根据 \(val_{id}\)\(k\) 的大小关系判断是左子树还是右子树

总时间复杂度 \(O(n\log^2n)\)

view code
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,inf=1e9;
#define ll long long
const ll Inf=0x3f3f3f3f3f3f3f3f;
int n,g[N],w[N],fa[N];
ll pre[N],suf[N],k;
vector<int> e[N];
#define pb push_back
int stac[N],top;
namespace SGT{
	#define lid (id<<1)
	#define rid (id<<1|1)
	#define mid ((l+r)>>1)
	ll mn1[N<<2],mn2[N<<2],val[N<<2],tag[N<<2];
	inline void add(int id,ll a){
		mn2[id]+=a;val[id]-=a;tag[id]+=a;
	}
	inline void pushdown(int id){
		add(lid,tag[id]);
		add(rid,tag[id]);
		tag[id]=0;
	}
	ll calc(int id,int l,int r,ll mn){
		if(l==r)return mn1[id]-mn;
		pushdown(id);
		if(mn>=mn2[lid])return min(val[id],calc(lid,l,mid,mn));
		else return min(mn1[lid]-mn,calc(rid,mid+1,r,mn));
	}
	inline void update(int id,int l,int r){
		mn1[id]=min(mn1[lid],mn1[rid]);
		mn2[id]=min(mn2[lid],mn2[rid]);
		val[id]=calc(rid,mid+1,r,mn2[lid]);
	}
	void build(int id,int l,int r){
		if(l==r){
			mn1[id]=mn2[id]=suf[l];
			val[id]=0;
			return;
		}
		build(lid,l,mid);build(rid,mid+1,r);
		update(id,l,r);
	}
	void modify(int id,int l,int r,int L,int R,ll val){
		if(L<=l&&r<=R){add(id,val);return;}
		pushdown(id);
		if(L<=mid)modify(lid,l,mid,L,R,val);
		if(R>mid)modify(rid,mid+1,r,L,R,val);
		update(id,l,r);
	}
	int query1(int id,int l,int r,ll lim){
		if(l==r)return mn1[id]<=lim?l:0;
		pushdown(id);
		if(mn1[rid]<=lim)return query1(rid,mid+1,r,lim);
		else return query1(lid,l,mid,lim);
	}
	int query(int id,int l,int r,ll mn){
		if(l==r)return mn1[id]-mn<=k?l:0;
		pushdown(id);
		if(mn<=mn2[lid])
			return max(query1(lid,l,mid,k+mn),query(rid,mid+1,r,mn));
		else{
			if(val[id]<=k)return query(rid,mid+1,r,mn2[lid]);
			else return query(lid,l,mid,mn);
		}
	}
}
int ans=0;
void dfs(int u){
	stac[++top]=u;
	if(u!=n+1&&fa[u]<=n)
		SGT::modify(1,1,n,fa[u]-1,n,pre[u]-pre[fa[u]]);
	if(u!=n+1){
		int l=1,r=top-1,ret=1;
		while(l<=r){
			if(pre[stac[mid]]-pre[u]>k)ret=mid,l=mid+1;
			else r=mid-1;
		}
		ret=stac[ret]-1;
		if(u>1)SGT::modify(1,1,n,1,u-1,Inf);
		SGT::modify(1,1,n,ret,n,-Inf);
		ans=max(ans,SGT::query(1,1,n,Inf)-u+1);
		if(u>1)SGT::modify(1,1,n,1,u-1,-Inf);
		SGT::modify(1,1,n,ret,n,Inf);
	}
	for(int v:e[u])dfs(v);
	if(u!=n+1&&fa[u]<=n)
		SGT::modify(1,1,n,fa[u]-1,n,-pre[u]+pre[fa[u]]);
	--top;
}
int main(){
	n=read();k=read();
	for(int i=1;i<n;++i)w[i]=read();
	for(int i=1;i<=n;++i)g[i]=read();
	for(int i=2;i<=n;++i){
		pre[i]=pre[i-1]+w[i-1]-g[i-1];
		suf[i]=suf[i-1]+w[i-1]-g[i];
		
	}
	pre[n+1]=suf[n+1]=Inf;stac[top=1]=n+1;
	for(int i=n;i>=1;--i){
		while(pre[stac[top]]<=pre[i])--top;
		fa[i]=stac[top];e[stac[top]].pb(i);
		stac[++top]=i;
	}
	top=0;
	SGT::build(1,1,n);
	dfs(n+1);
	printf("%d\n",ans);
	return 0;
}