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的主要内容,如果未能解决你的问题,请参考以下文章