题目大意

有\(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. jmeter汉化

    测试需要许多工具辅助工作,我们在使用这些工具之前需要对工具有一定的了解,第一步就是需要安装特定的软件. 因为工具基本上都是英文版的,所以菜鸟使用就面临汉化的问题,下面介绍几种汉化的办法: 一.万事不决 ...

  2. flask中的宏

    对于flask中的宏编程.我们使用 macro 来对宏起个名称 宏编程 对于我们来说是减少了代码的重用.以及简化了标签的操作,对与开发效率有很大的提升, 在html中.相信大多数都用到了.input ...

  3. halcon算子之tuple_gen_const,用于生成特定长度的元组并且初始化其元素

    原文地址:http://blog.sina.com.cn/s/blog_d38f8be50102wczk.html 函数原型: tuple_gen_const(: : Length, Const : ...

  4. 关于java学习中的一些易错点(基础篇)

    由JVM来负责Java程序在该系统中的运行,不同的操作系统需要安装不同的JVM,这样Java程序只需要跟JVM打交道,底层的操作由JVM去执行. JRE(Java Runtime Environmen ...

  5. Netty源码分析第6章(解码器)---->第4节: 分隔符解码器

    Netty源码分析第六章: 解码器 第四节: 分隔符解码器 基于分隔符解码器DelimiterBasedFrameDecoder, 是按照指定分隔符进行解码的解码器, 通过分隔符, 可以将二进制流拆分 ...

  6. Gitlab CI-1.Gitlab部署

    参考文档: GitLab Documentation:https://docs.gitlab.com/ce/ Installation and Configuration using omnibus ...

  7. HTML和JS自解码机制

    1.HTML 如果用户的输入被嵌入到HTML代码中,会进行HTML解码 解码方式包括: 1.HTML实体编码: 假定下面onclick后面的内容是可控的 点击按钮 2.进制编码: 点击按钮 2.JS ...

  8. 7.openldap使用ssl加密认证

    作者:yaoyao 1.服务器端部署 1.自建CA中心 1.CA中心生成自身私钥 #cd /etc/pki/CA #(umask 077; openssl genrsa -out private/ca ...

  9. Xcode中的文件类型

    文件类型 Xcode中的文件类型,总共4种类型: 1 普通文件(File) 2 Group(在Xcode中就是黄色的文件夹) 3 Folder(在Xcode中就是蓝色的文件夹) 4 Framework ...

  10. Beta周王者荣耀交流协会第一次Scrum会议

    1.立会照片 成员王超,高远博,冉华,王磊,王玉玲,任思佳,袁玥全部到齐. master:王超 2.时间跨度: 2017年11月10日 15:10 — 15:50 ,总计40分钟. 3.地 点: 一食 ...