码迷,mamicode.com
首页 > 其他好文 > 详细

CF671E

时间:2020-04-19 13:14:28      阅读:73      评论:0      收藏:0      [点我收藏+]

标签:||   ++i   单调栈   条件   部分   信息   情况下   efi   处理   

传送门

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

也是兔队博客里的一道题

先设两个数组\(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\),那么应该存在\(l\le p\le r\)满足\(SUF[r]>SUF[p]\)

我们令\(sufmin(l,r)=min\{SUF[i]\}(l\le 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]\}(l\le i<p)\),发现这个\(l\le i\)不好直接在线段树上搞

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

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

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

求最大的\(i\)使得\(c_i\le 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\n",Ans);
	}
}
signed main()
{
	red::main();
	return 0;
}

CF671E

标签:||   ++i   单调栈   条件   部分   信息   情况下   efi   处理   

原文地址:https://www.cnblogs.com/knife-rose/p/12730874.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!