【CF671E】Organizing a Race

题意:n个城市排成一排,每个城市内都有一个加油站,赛车每次经过第i个城市时都会获得$g_i$升油。相邻两个城市之间由道路连接,第i个城市和第i+1个城市之间的道路长度为$w_i$,走一单位的路要花1升油。你想在某两个城市之间举办一场锦标赛。如果你选择的两个城市分别是a和b(a<b),则具体过程如下:

1. 赛车从a开始往右走一直走到b,走过城市时会在加油站加油,走过道路时会消耗油,且一开始时就已经在a处加完油了。你需要满足赛车能有足够的油能从a走到b,即不能出现在走到道路的中途时出现没有油的情况。

2. 赛车从b开始往左走一直走到a,过程同上。

你可以认为赛车的油箱是无限大的。

一场锦标赛所经过的城市越多,则这场锦标赛就越成功,即你希望最大化b-a+1。

现在你有k个机会,每个机会是:你可以使任意一个城市的$g_i$增加1。现在你需要合理利用这k次机会,从而最大化b-a+1。

$n\le 100000,k,w_i,g_i\le 10^9$

题解:先考虑从a走到b的这段。我们先维护个前缀和:pre[i]=pre[i-1]+g[i]-w[i]。则在不加油的情况下,一辆车i最远能走到的j 就是 i右面第一个满足pre[j-1]<pre[i-1]的j,我们可以用单调栈来搞一搞,并设i右面第一个走不到的j为next[i]。从i走到next[i]需要的花费就是pre[i-1]-pre[next[i]-1]。根据贪心的想法,如果我们最终选择的城市是a和b,那么在从a走到b的途中,我们应尽可能给右边的城市增加权值。即我们每次可以直接走到next,然后给next的权值增加pre[i-1]-pre[next-1]即可。

下面是一步非常神的操作,我们将所有的i和next[i]连边。然后DFS一遍这棵树,假如当前走到了i。我们令cost[j]表示从i沿着next一直走到j需要的花费,那么如何维护cost[j]呢?我们在进入i这棵子树的时候,将next[i]..n的所有cost都增加,在退出i的子树时再将cost都减回去,则用线段树维护即可。现在我们已经知道了往右走的花费,那如何计算往左走的花费呢?我们再维护个前缀和:suf[i]=suf[i-1]+g[i]-w[i-1](修改时用线段树维护)。根据贪心,如果我们在返回来时需要花费k次机会,则一定是在一开始就直接用完所有的机会。那么从j返回i的花费就是$max\{suf[k],i\le k< j\}-suf[j]$,总花费就是$max\{suf[k],i\le k< j\}-suf[j]+cost[j]$。

现在我们要做的就是找出右面最后一个满足$max\{suf[k],i\le k< j\}-suf[j]+cost[j]\le K$的j。但是左边这坨东西如何搞呢?

我们在线段树上维护这3个东西:

max_p[x]:令p[i]表示cost[i]-suf[i]。max_p[x]维护区间内p的最大值。

max_suf[x]:区间x内suf的最大值。

max_s[x]:如果当前区间是[l,r],则$max_s[x]=min\{max\{suf[j],l\le j<i\}+p[i],mid<i\le r\}$。

具体维护过程过于复杂,请见代码。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define lson x<<1
#define rson x<<1|1
using namespace std; const int maxn=100010;
typedef long long ll;
int n,K,top,cnt,ans;
int st[maxn],to[maxn],nxt[maxn],head[maxn],nt[maxn];
ll g[maxn],w[maxn],pre[maxn],suf[maxn];
ll mp[maxn<<2],sp[maxn<<2]; //mp:max_p(p=cost-suf),sp:min_{max_suf{l..j-1}+p}
ll ms[maxn<<2],tag[maxn<<2]; //ms:max_suf,tag:区间+标记,cost+=tag,suf+=tag,所以p不变。
inline void add(int,int);
inline void upd(int,ll);
inline void pushdown(int);
ll calc(int,int,int,ll);
inline void pushup(int,int,int);
void build(int,int,int);
void updata(int,int,int,int,int,ll);
int solve(int,int,int,ll);
int query(int,int,int,ll);
void dfs(int);
inline int rd();
int main()
{
memset(head,-1,sizeof(head));
n=rd(),K=rd();
int i;
for(i=1;i<n;i++) w[i]=rd();
w[n]=1e17;
for(i=1;i<=n;i++) g[i]=rd(),pre[i]=pre[i-1]+g[i]-w[i],suf[i]=suf[i-1]+g[i]-w[i-1]; //预处里pre,suf
for(st[top=0]=n,i=n-1;i>=0;i--) //求next
{
while(top&&pre[st[top]]>=pre[i]) top--;
nt[i+1]=st[top]+1,add(st[top]+1,i+1),st[++top]=i;
}
build(1,n,1);
top=0,dfs(n+1);
printf("%d",ans);
return 0;
}
//------------------------------按照顺序从往下看------------------------------ inline void add(int a,int b) //略
{
to[cnt]=b,nxt[cnt]=head[a],head[a]=cnt++;
}
void dfs(int x) //首先按照之前说的,我们先建出next树,然后遍历next树。
{
st[++top]=x;
if(x!=n+1)
{
updata(1,n,1,1,x-1,-1e17); //排除掉i左面的点的干扰
updata(1,n,1,nt[x]-1,n,pre[x-1]-pre[nt[x]-1]); //维护cost和suf
int l=1,r=top,mid;
while(l<r)
{
mid=(l+r)>>1;
if(pre[x-1]-pre[st[mid]-1]<=K) r=mid;
else l=mid+1;
}
updata(1,n,1,st[r-1],n,1e17); //二分,排除掉i右面过远的点的干扰(如果往右走过不去,则不考虑往左走的情况)。
ans=max(ans,query(1,n,1,-1e17)-x+1); //更新答案
updata(1,n,1,st[r-1],n,-1e17); //复原
updata(1,n,1,1,x-1,1e17);
}
for(int i=head[x];i!=-1;i=nxt[i]) dfs(to[i]);
if(x!=n+1)
{
updata(1,n,1,nt[x]-1,n,-(pre[x-1]-pre[nt[x]-1])); //复原
}
top--;
}
void build(int l,int r,int x) //预处理结束时,构建线段树。
{
if(l==r)
{
ms[x]=suf[l];
mp[x]=-suf[l];
return ;
}
int mid=(l+r)>>1;
build(l,mid,lson),build(mid+1,r,rson);
pushup(l,r,x),mp[x]=min(mp[lson],mp[rson]);
}
void updata(int l,int r,int x,int a,int b,ll t) //区间加操作也跟普通线段树没什么区别。
{
if(a>b) return ;
if(a<=l&&r<=b)
{
upd(x,t);
return ;
}
pushdown(x);
int mid=(l+r)>>1;
if(a<=mid) updata(l,mid,lson,a,b,t);
if(b>mid) updata(mid+1,r,rson,a,b,t);
pushup(l,r,x);
}
inline void pushup(int l,int r,int x) //pushup和pushdown两个操作慢慢讲。
{
ms[x]=max(ms[lson],ms[rson]); //ms(max_suf):直接取最值即可,max_p:由于永远不会改变,所以不用维护。
int mid=(l+r)>>1;
sp[x]=calc(mid+1,r,rson,ms[lson]); //sp数组维护起来比较复杂,我们引入calc函数,下面讲。
}
inline void pushdown(int x) //pushdown比较简单
{
if(tag[x])
{
upd(lson,tag[x]),upd(rson,tag[x]);
tag[x]=0;
}
}
inline void upd(int x,ll y) //比较简单
{
tag[x]+=y,ms[x]+=y,sp[x]+=y;
}
ll calc(int l,int r,int x,ll t) //***关键函数*** calc(...,t)=min{max(max_suf{l..i-1},t)+p[i],l<=i<=r} 即我们已知了左边
//的max_suf,现在要求这个区间中答案的最小值。如何计算呢?
{
if(l==r) return t+mp[x];
pushdown(x);
int mid=(l+r)>>1;
if(ms[lson]>=t) return min(calc(l,mid,lson,t),sp[x]); //如果max_suf{l,mid}>=t,则t对[mid+1,r]的答案都没有影响,
//所以直接调用之前的答案sp即可(注意sp维护的是什么!)。
//然后我们只递归左边就行了。
return min(t+mp[lson],calc(mid+1,r,rson,t)); //否则,左边的max_suf{l..i-1}都应该取t,则用t+max_p{l,mid}更新答案
//然后只递归右面就行了。
} //整个calc的复杂度是O(log)的。 //--------------------分割线-------------------- 上面主要是修改,下面主要是查询。 int query(int l,int r,int x,ll t) //***关键函数*** 查询函数(即树上二分操作),我们想找到最右面那个答案<=m的点
//t的定义和calc()里的一样,我们已知了左边的max_suf{l..i-1}=t。实现过程也和calc类似。
{
if(l==r) return t+mp[x]<=K?l:0;
pushdown(x);
int mid=(l+r)>>1;
if(ms[lson]>=t) //讨论:如果max_suf{l,mid}>=t,则t对[mid+1,r]没有影响,我们可以直接调用sp数组。
{
if(sp[x]<=K) return query(mid+1,r,rson,ms[lson]); //如果[mid+1,r]中的最小值<=K,显然我们应该进入右面查询。
else return query(l,mid,lson,t); //否则呢,显然右面的都不合法,我们进入左边查询。
}
else //如果max_suf{l,mid}<t,则左面的max_suf都应该取t,我们引入solve函数,表示的就是
//当一个区间的max_suf{..i-1}都取t时的查询结果。而对于右面的,我们还需要递归查询。
{
return max(solve(l,mid,lson,t),query(mid+1,r,rson,t));
}
}
int solve(int l,int r,int x,ll t) //说白了就是已知区间的max_suf{..i-1}=t时的query函数,但是相对简单一些。
{
if(l==r) return t+mp[x]<=K?l:0;
pushdown(x);
int mid=(l+r)>>1;
if(t+mp[rson]<=K) return solve(mid+1,r,rson,t); //如果右边的答案<=K,则去右面
return solve(l,mid,lson,t); //否则去左边
} //一次solve的复杂度是O(log)的
inline int rd()
{
int ret=0,f=1; char gc=getchar();
while(gc<'0'||gc>'9') {if(gc=='-') f=-f; gc=getchar();}
while(gc>='0'&&gc<='9') ret=ret*10+(gc^'0'),gc=getchar();
return ret*f;
}
//所以呢,我们的总复杂度就是O(n\log^2n)的。

【CF671E】Organizing a Race 单调栈+线段树的更多相关文章

  1. 洛谷P4198 楼房重建 单调栈+线段树

    正解:单调栈+线段树 解题报告: 传送门! 首先考虑不修改的话就是个单调栈板子题昂,这个就是 然后这题的话,,,我怎么记得之前考试好像有次考到了类似的题目昂,,,?反正我总觉着这方法似曾相识的样子,, ...

  2. 2018宁夏邀请赛 L Continuous Intervals(单调栈+线段树)

    2018宁夏邀请赛 L Continuous Intervals(单调栈+线段树) 传送门:https://nanti.jisuanke.com/t/41296 题意: 给一个数列A 问在数列A中有多 ...

  3. The Preliminary Contest for ICPC China Nanchang National Invitational I. Max answer (单调栈+线段树)

    题目链接:https://nanti.jisuanke.com/t/38228 题目大意:一个区间的值等于该区间的和乘以区间的最小值.给出一个含有n个数的序列(序列的值有正有负),找到该序列的区间最大 ...

  4. 2019南昌网络赛-I(单调栈+线段树)

    题目链接:https://nanti.jisuanke.com/t/38228 题意:定义一段区间的值为该区间的和×该区间的最小值,求给定数组的最大的区间值. 思路:比赛时还不会线段树,和队友在这题上 ...

  5. 网络赛 I题 Max answer 单调栈+线段树

    题目链接:https://nanti.jisuanke.com/t/38228 题意:在给出的序列里面找一个区间,使区间最小值乘以区间和得到的值最大,输出这个最大值. 思路:我们枚举每一个数字,假设是 ...

  6. 南昌邀请赛I.Max answer 单调栈+线段树

    题目链接:https://nanti.jisuanke.com/t/38228 Alice has a magic array. She suggests that the value of a in ...

  7. [CF1083D]The Fair Nut’s getting crazy[单调栈+线段树]

    题意 给定一个长度为 \(n\) 的序列 \(\{a_i\}\).你需要从该序列中选出两个非空的子段,这两个子段满足 两个子段非包含关系. 两个子段存在交. 位于两个子段交中的元素在每个子段中只能出现 ...

  8. 南昌网络赛 I. Max answer (单调栈 + 线段树)

    https://nanti.jisuanke.com/t/38228 题意给你一个序列,对于每个连续子区间,有一个价值,等与这个区间和×区间最小值,求所有子区间的最大价值是多少. 分析:我们先用单调栈 ...

  9. 2019ICPC南昌邀请赛网络赛 I. Max answer (单调栈+线段树/笛卡尔树)

    题目链接 题意:求一个序列的最大的(区间最小值*区间和) 线段树做法:用单调栈求出每个数两边比它大的左右边界,然后用线段树求出每段区间的和sum.最小前缀lsum.最小后缀rsum,枚举每个数a[i] ...

随机推荐

  1. 使用 StoryBoard 实现左右按钮切换图片的浏览效果

    关键技能:使用故事板进行布局时,点击选中控件(组件)并按住 control 键向某个方向拖动,产生一条实线,然后弹出的窗口可以设置控件(组件)的布局约束条件:从而实现自动布局 AutoLayout 效 ...

  2. css只改变input输入框光标颜色不改变文字颜色实现方法

    input:focus{color:blue}//光标颜色 input{ text-shadow: 0px 0px 0px red;//文字颜色 -webkit-text-fill-color: tr ...

  3. mySql慢查询分析原因

    1.分析查询慢的语句,并记录到日志中 查看: http://blog.csdn.net/haiqiao_2010/article/details/25138099

  4. centos图形界面的开启和关闭

    一般来说centos主要用于服务器端,所以很少开启图形化界面,但是有时候为了工作方便也会偶尔开启图形界面,下面就让简单谈谈如何开启图形化界面, 当然简化安装是没有这个程序的需要自行安装,可以yum安装 ...

  5. win7 IIS7 发布网站遇到 HTTP 错误 500.19 由于权限不足

    win7 IIS7 发布网站遇到 HTTP 错误 500.19 - Internal Server Error 一看是5xx错误,就知道是服务器的问题,网上查了下,原来是权限问题 解决方法       ...

  6. 自动构建工具Gulp

    摘要:  gulp与grunt一样,都是自动构建工具.和grunt相比它更突出一个流的概念,任务是一个接一个执行的.今天就分享如何用gulp做自动化. 安装: gulp也是基于node环境,所以安装g ...

  7. AWS系列-创建 IAM 用户

    创建 IAM 用户(控制台) 官方文档 https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/introduction.html 通过 AWS ...

  8. SpringBoot------注解把配置文件自动映射到属性和实体类

    1.映射到属性 package top.ytheng.demo.controller; import org.springframework.beans.factory.annotation.Valu ...

  9. HybridApp启动引导页的实现

    有一种帅叫做长话短说,@孙红雷,--这可以叫做“短帅”吗,^_^ 首先说下思路,既然是Hybrid APP, 那就是可以用html的方式实现,启动引导页比较常见的展示方式是滑动,那么我们就可以使用图片 ...

  10. SpringMVC由浅入深day01_10@RequestMapping_11controller方法的返回值

    10 @RequestMapping 10.1 Url路径映射 @RequestMapping(value="/item")或@RequestMapping("/item ...