题目大意

有\(n\)个加油站排成一行,编号为\(1\sim n\) ,\(i\)与\(i+1\)间有一条长为\(w_i\)千米的道路。

一辆汽车在经过加油站\(i\)时会得到\(g_i\)升汽油 , 假设汽车每行驶一千米需要消耗一升汽油。

现在要求选定两个合法的加油站 \(i\) 、\(j\), 且 \(i\le j\),使得一辆没有油的汽车能从\(i\)出发行驶到 \(j\),也能从\(j\)出发行驶到\(i\) 。

你有\(K\)次操作,每次操作能使选定一个\(i\) , 使\(g_i\)增加 \(1\)。

对于所有合法的\(i\)与\(j\) ,求\(j − i + 1\)的最大值。数据范围:\(n\leq 10^5\) , \(w,g,K\leq 10^9\)。

题解

化简限制条件

令\(pre_i = pre_{i-1} + g_i - w_i\)、\(suf_i = suf_{i-1} + g_i - w_{i-1}\)。

考虑枚举一个左端点\(l\),如何判断一个右端点\(r\)合法?

首先保证能够从\(l\)走到\(r\),即对于\(k\in [l,r)\),满足\(pre_{k-1}-pre_{l-1} \ge 0\),贪心能走就走即可。

设只满足从左走到右需要付出的代价为\(cost_{l,r}\),设付出这些代价后,\(suf\)值变成了\(suf'\)。

为了满足从右边能够走到左边,显然贪心只只在右端点进行改造是最优的。

故一个\([l,r]\)合法的条件为:\(cost_{l,r}\leq K\),\(max(suf'_k | k\in [l,r)) - suf_{l}' + cost_{l,r} \leq K\)。

一些预处理与转化

注意到若不付出代价且\(i\)不能走到\(j\)(只考虑左到右的限制),则\(pre_{j-1} - pre_{i-1} < 0\)。

即\(pre_{i-1} > pre_{j-1}\),令\(nxt_i = min(j|pre_{i-1} > pre_{j-1})\),则\(i\to nxt_i\)形成了一棵树型结构。

我们在这棵树上进行遍历,当\(dfs\)到\(u\)时,就可以知道\(cost_{u,v} = pre_{u-1}-pre_{t-1}\),其中\(t\)为\(u\)满足\(t\leq v\)的最浅祖先。

当位于\(u\)点时,我们计算以\(u\)为左端点的最大答案。

那么即求\(Ans_u = max(r|cost_{l,r}\leq K,max(suf_k'|k\in [u,r))-suf'k+cost_{u,r})\)。

线段树维护什么

考虑用线段树支持上述操作,对于区间\([l,r]\)的线段树结点,维护:

  • \(Minp_{l,r} = min(-suf_k' + cost_{u,k}|k\in [l,r])\),可以发现这个值对于任意\(u\)都不变。

    因为\(dfs\)过程中,若从\(u\to v\),则\(suf'_k\)与\(cost_{u,k}\)的变化量都是\(+ pre_{v-1}-pre_{u-1}\),所以预处理即可。

    令\(P_{l,r} = min(-suf_k|k\in [l,r]) = Minp_{l,r}\)。
  • \(Maxs_{l,r} = max(suf_k' | k\in [l,r])\),维护就是普通区间加法,区间求\(max\)。
  • \(ans_{l,r} = min(\ max(suf_k'|k\in [l,j)) + P_j\ |\ j\in (mid,r]\ )\)。

下面详细介绍一下如何维护\(ans_{l,r}\)、以及利用这些东西在\(dfs\)到\(u\)时查询\(Ans_u\)。

数组\(ans\)的维护

考虑如何求\(ans_{L,R} = min(\ max(suf_k'|k\in [L,j)) + P_j\ |\ j\in (mid,R]\ )\)。

类似线段树维护单调栈,考虑递归处理。

设当前处理到\([l,r]\),令\(x = max(suf'_k | k\in [L,l))\),令\(y = Maxs_{l,mid}\)。

  • 若\(x\ge y\),则\(j\in [l,mid)\)中的\(max(suf_k'|k\in [L,j)) = x\),答案为\(x + minp_{l,mid}\)。

    递归处理\((mid,r]\)的答案。
  • 若\(x \leq y\),则\(x\)对\((mid,r]\)的答案无影响,即\(j\in (mid,r]\)的答案为\(ans_{l,r}\)。

    递归处理\([l,mid]\)的答案。

每次\(PushUp\)的复杂度为\(O(logn)\),故修改复杂度为\(O(nlog^2n)\)。

查询答案\(Ans_u\)

首先二分得到最大的满足\(cost_{u,r}\leq K\)的\(pr\),把\([pr,n]\)的\(suf'\)加上\(inf\),即把\((pr,n]\)剔除出去。

然后把\([1,u)\)的\(suf'\)都加上\(-inf\),消去它们对答案的影响。

上面哪些都是为了方便操作,下面来看具体如何查询。

设当前查询区间\([L,R]\)的答案,令\(x = max(suf'_k|k\in [1,L))\),令\(y = Maxs_{L,mid}\)。

  • 若\(x\ge y\),则\(j\in [L,mid]\)的\(max(suf'_k|k\in [1,j)) = x\)为定值,线段树上二分即可。

    递归处理\((mid,R]\)的答案。
  • 若\(x\leq y\),则\(x\)对\((mid+1,R]\)的答案无影响,若\(ans_{l,r}\leq K\)则递归\((mid,R]\),否则递归\([L,mid]\)。

单次查询的最坏复杂度为\(O(log^2n)\),故查询总复杂度为\(O(nlog^2n)\)。

实现代码

#include<bits/stdc++.h>
#define IL inline
#define _ 200005
#define ll long long
#define RG register
using namespace std ; IL ll gi() {
RG ll data = 0 , fu = 1 ; RG char ch = getchar() ;
while(ch != '-' && (ch < '0' || ch > '9')) ch = getchar() ; if(ch == '-') fu = -1 , ch = getchar() ;
while('0' <= ch && ch <= '9') data = (data << 1) + (data << 3) + (ch ^ 48) , ch = getchar() ; return fu * data ;
} const ll inf = 1e16 ; int stk[_] , nxt[_] , n , K ; ll pre[_] , suf[_] , W[_] , g[_] , root ; int Ans ;
vector<int> edge[_] ; namespace Seg {
ll Minp[_ << 2] , Maxs[_ << 2] , ans[_ << 2] , tag[_ << 2] ;
IL void Add(int o , ll v) {
Maxs[o] += v ; tag[o] += v ; ans[o] += v ;
return ;
}
IL void PushDown(int o) {
if(tag[o] == 0) return ;
Add(o << 1 , tag[o]) ; Add(o << 1 | 1 , tag[o]) ; tag[o] = 0 ;
}
ll Calc(int o , int l , int r , ll x) {
if(l == r) return x + Minp[o] ; int mid = (l + r) >> 1 ;
PushDown(o) ;
ll y = Maxs[o << 1] ;
if(x >= y) return min(x + Minp[o << 1] , Calc(o << 1 | 1 , mid + 1 , r , x)) ;
else return min(ans[o] , Calc(o << 1 , l , mid , x)) ;
}
IL void PushUp(int o , int l , int r) {
Maxs[o] = max(Maxs[o << 1] , Maxs[o << 1 | 1]) ;
int mid = (l + r) >> 1 ;
ans[o] = Calc(o << 1 | 1 , mid + 1 , r , Maxs[o << 1]) ;
}
void Build(int o , int l , int r) {
if(l == r) {
Minp[o] = -suf[l] ; Maxs[o] = suf[l] ; ans[o] = 0 ;
return ;
}
int mid = (l + r) >> 1 ;
Build(o << 1 , l , mid) ; Build(o << 1 | 1 , mid + 1 , r) ; PushUp(o , l , r) ;
Minp[o] = min(Minp[o << 1] , Minp[o << 1 | 1]) ;
}
IL void Insert(int o , int l , int r , int ql , int qr , ll v) {
if(ql <= l && r <= qr) {Add(o , v) ; return ; }
int mid = (l + r) >> 1 ;
PushDown(o) ;
if(ql <= mid) Insert(o << 1 , l , mid , ql , qr , v) ;
if(qr > mid) Insert(o << 1 | 1 , mid + 1 , r , ql , qr , v) ;
PushUp(o , l , r) ;
}
int Solve(int o , int l , int r , ll x) {
if(l == r) return x + Minp[o] <= K ? l : 0 ;
int mid = (l + r) >> 1 ;
PushDown(o) ;
if(x + Minp[o << 1 | 1] <= K) return Solve(o << 1 | 1 , mid + 1 , r , x) ;
else return Solve(o << 1 , l , mid , x) ;
}
int Query(int o , int l , int r , ll x) {
if(l == r) {
return x + Minp[o] <= K ? l : 0 ;
}
int mid = (l + r) >> 1 ; PushDown(o) ;
ll y = Maxs[o << 1] ;
int ret = 0 ;
if(x >= y) {
ret = Query(o << 1 | 1 , mid + 1 , r , x) ;
if(x + Minp[o] <= K) ret = max(ret , Solve(o << 1 , l , mid , x)) ;
}
else if(x <= y) {
if(ans[o] <= K) ret = Query(o << 1 | 1 , mid + 1 , r , y) ;
else ret = Query(o << 1 , l , mid , x) ;
}
return ret ;
}
} IL void Pre() {
stk[0] = 0 ;
for(int i = n; i >= 1; i --) {
while(stk[0] && pre[i - 1] <= pre[stk[stk[0]] - 1]) -- stk[0] ;
nxt[i] = stk[0] ? stk[stk[0]] : n + 1 ;
stk[++ stk[0]] = i ;
}
root = n + 1 ; nxt[root] = n + 1 ;
for(int i = 1; i <= n; i ++) edge[nxt[i]].push_back(i) ; return ;
}
IL int Bipart(int x) {
int l = 2 , r = stk[0] , ret = 1 ;
while(l <= r) {
int mid = (l + r) >> 1 ;
if(pre[x - 1] - pre[stk[mid] - 1] <= K) ret = mid , r = mid - 1 ;
else l = mid + 1 ;
}
return stk[ret - 1] - 1 ;
}
void Dfs(int u , int From) {
stk[++ stk[0]] = u ;
if(nxt[u] <= n) Seg::Insert(1 , 1 , n , nxt[u] - 1 , n , pre[u - 1] - pre[nxt[u] - 1]) ;
if(u <= n) {
int r = Bipart(u) ;
if(r < n) Seg::Insert(1 , 1 , n , r , n , inf) ; if(u != 1) Seg::Insert(1 , 1 , n , 1 , u - 1 , -inf) ;
Ans = max(Ans , Seg::Query(1 , 1 , n , -inf) - u + 1) ;
if(r < n) Seg::Insert(1 , 1 , n , r , n , -inf) ; if(u != 1) Seg::Insert(1 , 1 , n , 1 , u - 1 , inf) ;
}
for(auto v : edge[u]) if(v != From) Dfs(v , u) ;
-- stk[0] ;
if(nxt[u] <= n) Seg::Insert(1 , 1 , n , nxt[u] - 1 , n , pre[nxt[u] - 1] - pre[u - 1]) ;
} int main() {
n = gi() ; K = gi() ;
for(int i = 1; i < n; i ++) W[i] = gi() ; W[n] = inf ;
for(int i = 1; i <= n; i ++) g[i] = gi() ;
for(int i = 1; i <= n; i ++) pre[i] = pre[i - 1] + g[i] - W[i] ;
for(int i = 1; i <= n; i ++) suf[i] = suf[i - 1] + g[i] - W[i - 1] ;
Pre() ;
Seg::Build(1 , 1 , n) ;
stk[0] = 0 ; Dfs(root , 0) ;
cout << Ans << endl ; return 0 ;
}

[CF671E] Organizing a Race的更多相关文章

  1. 【CF671E】Organizing a Race 单调栈+线段树

    [CF671E]Organizing a Race 题意:n个城市排成一排,每个城市内都有一个加油站,赛车每次经过第i个城市时都会获得$g_i$升油.相邻两个城市之间由道路连接,第i个城市和第i+1个 ...

  2. 33 Introducing the Go Race Detector

    Introducing the Go Race Detector 26 June 2013 Introduction Race conditions are among the most insidi ...

  3. 20 Organizing Go code 组织go代码

    Organizing Go code 16 August 2012 Introduction Go code is organized differently to that of other lan ...

  4. Promise.race

    [Promise.race] 返回最先完成的promise var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 5 ...

  5. golang中的race检测

    golang中的race检测 由于golang中的go是非常方便的,加上函数又非常容易隐藏go. 所以很多时候,当我们写出一个程序的时候,我们并不知道这个程序在并发情况下会不会出现什么问题. 所以在本 ...

  6. 【BZOJ-2599】Race 点分治

    2599: [IOI2011]Race Time Limit: 70 Sec  Memory Limit: 128 MBSubmit: 2590  Solved: 769[Submit][Status ...

  7. Self Organizing Maps (SOM): 一种基于神经网络的聚类算法

    自组织映射神经网络, 即Self Organizing Maps (SOM), 可以对数据进行无监督学习聚类.它的思想很简单,本质上是一种只有输入层--隐藏层的神经网络.隐藏层中的一个节点代表一个需要 ...

  8. hdu 4123 Bob’s Race 树的直径+rmq+尺取

    Bob’s Race Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Probl ...

  9. Codeforces Round #131 (Div. 2) E. Relay Race dp

    题目链接: http://codeforces.com/problemset/problem/214/E Relay Race time limit per test4 secondsmemory l ...

随机推荐

  1. 使用Fiddler进行Web接口测试

    一.Fiddler简介1.为什么是Fiddler?抓包工具有很多,小到最常用的web调试工具firebug,达到通用的强大的抓包工具wireshark.为什么使用fiddler?原因如下: A)Fir ...

  2. Quartz.net 定时任务在IIS中没有定时执行

    问题:Quartz.net 定时任务在项目部署到IIS中发现没有定时执行 解决方案: 1.在服务器上装一个360(自带自动刷新功能),在工具——>自动刷新——>自动刷新勾上 然后再设置一下 ...

  3. Android Dalvik虚拟机初识

    摘自:http://blog.csdn.net/andyxm/article/details/6126907 首先,让我们来思考下面几个问题: 什么是Dalvik虚拟机? Dalvik VM与JVM有 ...

  4. svn树冲突的解决方法

    树冲突 就是开发人员移动.重命名.删除一个文件或文件夹,而另一名开发人员也对它们进行了移动.重命名.删除或者仅仅是修改时就会发生树冲突.有很多种不同的情形可以导致树冲突,而且不同的情形需要不同的步骤来 ...

  5. 有关JOIN ON的心得体会

    在第一家公司工作大概有一年之后,我的上司开始让我负责一个项目了. 说起这个项目,其实就是类似一个报表系统的抽数据的活.我的主要工作就是将我们公司产生的数据进行抽取清理,然后生成一些带有分析性质的数据. ...

  6. 炸!分享美团面试关于selenium的面试题

    个人分类: 软件测试 编辑 在这个互联网技术快速迭代的时代,每个测试员都知道技术对于职业发展的重要性,那些技术好的测试员不仅薪资高,而且大多数集中在一线互联网企业工作,让人感觉非常高大上的同时,也想去 ...

  7. Cocos2dx源码赏析(2)之渲染

    Cocos2dx源码赏析(2)之渲染 这篇,继续从源码的角度来跟踪下Cocos2dx引擎的渲染过程,以此来梳理下Cocos2dx引擎是如何将精灵等元素显示在屏幕上的. 从上一篇对Cocos2dx启动流 ...

  8. Python函数初识

    一.函数是什么 ​ 计算机语言中的函数是类比于数学中的函数演变来的,但是又有所不同.前面的知识中我们学会了运用基础语法(列表.字典)和流程控制语句貌似也能处理一些复杂的问题,但是相对于相似的大量重复性 ...

  9. cobbler部署以及使用

    常用软件安装及使用目录 资源链接:https://pan.baidu.com/s/1yfVnuSgY5vOTh-B74tpVyw   网盘分享的文件在此 cobbler第一次操作history. ec ...

  10. centos安装eclise启动报错

    A Java Runtime Environment (JRE) or Java Development Kit (JDK) must be avail http://blog.csdn.net/u0 ...