标签:|| ++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;
}
标签:|| ++i 单调栈 条件 部分 信息 情况下 efi 处理
原文地址:https://www.cnblogs.com/knife-rose/p/12730874.html