zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    lua中的冒号和点
    NuGet使用简要说明
    C#浅谈类实体与DataTable执行效率
    win8 使用技巧
    Bitnami Redmine 中文附件名 报错修复
    Markdown 测试
    几种常用网页返回顶部代码
    文字超出隐藏并显示省略号
    手机正则写法
    安卓内存管理相关关键字
  • 原文地址:https://www.cnblogs.com/knife-rose/p/12730874.html
Copyright © 2011-2022 走看看