CF671E

Posted knife-rose

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF671E相关的知识,希望对你有一定的参考价值。

传送门

嗯,转化起来巨长的一道题

也是兔队博客里的一道题

先设两个数组(pre[i])表示从(1)(i)需要额外花多少油,(suf[i])表示从(i)(1)需要额外花多少油

容易递推式(pre[i]=pre[i-1]-g[i-1]+w[i-1],suf[i]=suf[i-1]-g[i]+w[i-1])

理论上这两个数组最后都应该和(0)(max),不过实际上没必要

这两个数组是可减的,(pre[r]-pre[l])是从(l)走到(r)的花费,(suf[r]-suf[l])是从(r)走到(l)的花费

先考虑从左向右走,假设从一个位置(l)在不花费额外代价的情况下第一个达到不了的点是(nxt[l])

那么显然(pre[nxt[l]])(l)右边第一个大于(pre[l])的点

所以(nxt[l])我们可以用单调栈求出来

假设(val[i]=pre[nxt[l]]-pre[l]),即通过这段区域最少需要花费多少代价

如果单纯的要从左向右通过([l,nxt[l]])这段区间,这(val[i])个油可以加在([l,nxt[l]-1])的任何位置

但是考虑到我们一会还要从右往左走回来,所以放在(nxt[l]-1)的位置应该是最优的

此时可以顺利到达(nxt[l])位置,且此时邮箱为空,所以继续讨论(nxt[l],nxt[nxt[l]])的问题

我们再考虑从(r)(l)走的问题,我们可以优先算出(cost(l,r))表示从(l)走到(r)需要的最小代价,然后再把剩下的代价全部加到(r)位置

我们考虑刚才对(g_{nxt[l]-1})的各种加油操作会影响到原本的(suf)值,我们暂且称呼被影响后的(suf)值叫(SUF)

如果(r)不能直接走回(l),那么应该存在(lle ple r)满足(SUF[r]>SUF[p])

我们令(sufmin(l,r)=min{SUF[i]}(lle i<r))注意是左闭右开区间

设整个区间([l,r])往返的代价是(w(l,r))

那么(w(l,r)=cost(l,r)+max{0,SUF[r]-minsuf(l,r)})

我们要求一个区间满足要求,即(w(l,r)le k)

然而(max)操作不太好实现,我们也可以不进行取(max),改为判断(cost(l,r)le k)(cost(l,r)+SUF[r]-minsuf(l,r)le k)

我们观察到对于一个位置(l),集合(S_l={nxt[l],nxt[nxt[l]],……}) 对于(cost(l,r)) 函数是单调递增的

且所有((nxt[l],l))之间形成了一个树形结构,我们可以在树上向上二分或倍增找到满足(cost(l,r)le k)的右端点(r)

对于(w(l,r))的三部分:

对于 (cost(l,r))每一个(S_l)中的元素(x)都会对([x,r])造成一个加法的影响

对于 (SUF[r])每一个(S_l)中的元素(x)都会对([x,r])造成一个减法影响

然而这两部分的贡献刚好互为相反,所以都不用管~

对于(minsuf(l,r))每个(x)对[x-1,r]有一个后缀减(这里是(x-1)是因为(suf[i-1])是由(g[i])得到的)

所以对于(l),每个位置(p)的代价为(suf[l]-minsuf(l,p))考虑用线段树维护

(p)的取值范围为([l,r])

然而(minsuf(l,p)=min{SUF[i]}(lle i<p)),发现这个(lle i)不好直接在线段树上搞

我们可以在处理(l)的时候给(SUF[1~l-1])加上一个(inf),同样给(SUF[p~n])减去一个(inf)

这样把(p)的限制取消变成全局

最后变成了(c_i=suf_i-min{SUF[j]}(1le j<i))

求最大的(i)使得(c_ile k)

再简化一下(c_i=a_i-min{b_j}),支持(b_j)区间修改

维护三个信息:(a_i)区间最小值,(b_i)区间最小值,仅考虑当前区间的情况下右子树(c_i)的最小值

写出合并答案的函数(calc)

inline int calc(int l,int r,int p,int pre)
{
	if(l==r) return a[p]-pre;
	if(tag[p]) pushdown(p);
	if(b[ls(p)]<pre) return min(calc(l,mid,ls(p),pre),ans[p]);
	return min(a[ls(p)]-pre,calc(mid+1,r,rs(p),pre));
}

左子树最小值小于前缀最小值时,左子树中可能存在答案

左子树最小值大于前缀最小值时,左子树的贡献只能是(a[ls(p)]-pre),右子树中由于前缀最小值不同可能贡献有改变

inline int solve2(int l,int r,int p,int k)
{
	if(l==r) return l;
	return a[rs(p)]<=k?solve2(mid+1,r,rs(p),k):solve2(l,mid,ls(p),k);
}
inline int solve(int l,int r,int p,int &pre)
{
	if(l==r)
	{
		int ret=a[p]-pre<=k?l:0;
		pre=min(pre,b[p]);
		return ret;
	}
	if(tag[p]) pushdown(p);
	if(b[ls(p)]<pre)
	{
		if(ans[p]<=k) return solve(mid+1,r,rs(p),pre=b[ls(p)]);
		else
		{
			int ret=solve(l,mid,ls(p),pre);
			pre=min(pre,b[p]);
			return ret;
		}
	}
	else
	{
		int ret=a[ls(p)]<=k+pre?solve2(l,mid,ls(p),k+pre):0;
		return max(ret,solve(mid+1,r,rs(p),pre));
	}
}

如果左子树最小值小于前缀最小值

如果右子树的答案确定小于(k),那么把前缀最小值变成左子树最小值,递归右子树,否则递归左子树

如果左子树最小值大于前缀最小值

如果左子树的(a_i)最小值存在满足要求的条件,递归进入左子树寻找答案,然后再右子树寻找答案

然后就完成啦!贴上完整(code)

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define y1 qwq 
	inline int read()
	{
		int x=0;char ch,f=1;
		for(ch=getchar();(ch<‘0‘||ch>‘9‘)&&ch!=‘-‘;ch=getchar());
		if(ch==‘-‘) f=0,ch=getchar();
		while(ch>=‘0‘&&ch<=‘9‘){x=(x<<1)+(x<<3)+ch-‘0‘;ch=getchar();}
		return f?x:-x;
	}
	const int N=1e5+10,inf=0x3f3f3f3f3f3f3f3f;
	int n,k,Ans;
	int w[N],g[N],pre[N],suf[N],nxt[N];
	int st[N],top;
	vector<int> eg[N];
	int a[N<<2],b[N<<2],ans[N<<2],tag[N<<2];
	inline void pushdown(int p)
	{
		b[ls(p)]+=tag[p],b[rs(p)]+=tag[p];
		ans[ls(p)]-=tag[p],ans[rs(p)]-=tag[p];
		tag[ls(p)]+=tag[p],tag[rs(p)]+=tag[p];
		tag[p]=0;
	}
	inline int calc(int l,int r,int p,int pre)
	{
		if(l==r) return a[p]-pre;
		if(tag[p]) pushdown(p);
		if(b[ls(p)]<pre) return min(calc(l,mid,ls(p),pre),ans[p]);
		return min(a[ls(p)]-pre,calc(mid+1,r,rs(p),pre));
	}
	inline void build(int l,int r,int p)
	{
		if(l==r)
		{
			a[p]=b[p]=suf[l];
			return;
		}
		build(l,mid,ls(p));build(mid+1,r,rs(p));
		a[p]=min(a[ls(p)],a[rs(p)]);
		b[p]=min(b[ls(p)],b[rs(p)]);
		ans[p]=calc(mid+1,r,rs(p),b[ls(p)]);
	}
	inline void update(int tl,int tr,int l,int r,int p,int k)
	{
		if(tl<=l&&r<=tr)
		{
			b[p]+=k;
			ans[p]-=k;
			tag[p]+=k;
			return;
		}
		if(tag[p]) pushdown(p);
		if(tl<=mid) update(tl,tr,l,mid,ls(p),k);
		if(tr>mid) update(tl,tr,mid+1,r,rs(p),k);
		b[p]=min(b[ls(p)],b[rs(p)]);
		ans[p]=calc(mid+1,r,rs(p),b[ls(p)]);
	}
	inline int solve2(int l,int r,int p,int k)
	{
		if(l==r) return l;
		return a[rs(p)]<=k?solve2(mid+1,r,rs(p),k):solve2(l,mid,ls(p),k);
	}
	inline int solve(int l,int r,int p,int &pre)
	{
		if(l==r)
		{
			int ret=a[p]-pre<=k?l:0;
			pre=min(pre,b[p]);
			return ret;
		}
		if(tag[p]) pushdown(p);
		if(b[ls(p)]<pre)
		{
			if(ans[p]<=k) return solve(mid+1,r,rs(p),pre=b[ls(p)]);
			else
			{
				int ret=solve(l,mid,ls(p),pre);
				pre=min(pre,b[p]);
				return ret;
			}
		}
		else
		{
			int ret=a[ls(p)]<=k+pre?solve2(l,mid,ls(p),k+pre):0;
			return max(ret,solve(mid+1,r,rs(p),pre));
		}
	}
	inline void dfs(int now)
	{
		st[++top]=now;
		if(nxt[now]<=n) update(nxt[now]-1,n,1,n,1,pre[now]-pre[nxt[now]]);
		if(now<=n)
		{
			int l=2,r=top-1,ret=1;
			while(l<=r)
			{
				if(pre[st[mid]]-pre[now]>k) ret=mid,l=mid+1;
				else r=mid-1;
			}
			int rmax=st[ret]-1,_=inf;
			if(now>1) update(1,now-1,1,n,1,inf);
			update(rmax,n,1,n,1,-inf);
			int pos=solve(1,n,1,_);
			update(rmax,n,1,n,1,inf);
			if(now>1) update(1,now-1,1,n,1,-inf);
			Ans=max(Ans,pos-now+1);
		}
		for(auto t:eg[now]) dfs(t);
		if(nxt[now]<=n) update(nxt[now]-1,n,1,n,1,pre[nxt[now]]-pre[now]);
		--top;
	}
	inline void main()
	{
		n=read(),k=read();Ans=1;
		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]-g[i-1]+w[i-1];
			suf[i]=suf[i-1]-g[i]+w[i-1];
		}
		pre[n+1]=inf;
		nxt[n+1]=st[top=1]=n+1;
		for(int i=n;i;--i)
		{
			while(pre[st[top]]<=pre[i]) --top;
			nxt[i]=st[top],st[++top]=i;
			eg[nxt[i]].push_back(i);
		}
		top=0;
		build(1,n,1);
		dfs(n+1);
		printf("%lld
",Ans);
	}
}
signed main()
{
	red::main();
	return 0;
}

以上是关于CF671E的主要内容,如果未能解决你的问题,请参考以下文章

CF671E(线段树+单调栈)

CF671EOrganizing a Race 单调栈+线段树

如何从后台弹出片段

cf 模拟

CF1435 游记

无法解析符号 c882c94be45fff9d16a1cf845fc16ec5