[CF671E] Organizing a Race
题目大意
有\(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的更多相关文章
- 【CF671E】Organizing a Race 单调栈+线段树
		[CF671E]Organizing a Race 题意:n个城市排成一排,每个城市内都有一个加油站,赛车每次经过第i个城市时都会获得$g_i$升油.相邻两个城市之间由道路连接,第i个城市和第i+1个 ... 
- 33  Introducing the Go Race Detector
		Introducing the Go Race Detector 26 June 2013 Introduction Race conditions are among the most insidi ... 
- 20  Organizing Go code 组织go代码
		Organizing Go code 16 August 2012 Introduction Go code is organized differently to that of other lan ... 
- Promise.race
		[Promise.race] 返回最先完成的promise var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 5 ... 
- golang中的race检测
		golang中的race检测 由于golang中的go是非常方便的,加上函数又非常容易隐藏go. 所以很多时候,当我们写出一个程序的时候,我们并不知道这个程序在并发情况下会不会出现什么问题. 所以在本 ... 
- 【BZOJ-2599】Race      点分治
		2599: [IOI2011]Race Time Limit: 70 Sec Memory Limit: 128 MBSubmit: 2590 Solved: 769[Submit][Status ... 
- Self Organizing Maps (SOM): 一种基于神经网络的聚类算法
		自组织映射神经网络, 即Self Organizing Maps (SOM), 可以对数据进行无监督学习聚类.它的思想很简单,本质上是一种只有输入层--隐藏层的神经网络.隐藏层中的一个节点代表一个需要 ... 
- hdu 4123 Bob’s Race 树的直径+rmq+尺取
		Bob’s Race Time Limit: 5000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Probl ... 
- Codeforces Round #131 (Div. 2) E. Relay Race dp
		题目链接: http://codeforces.com/problemset/problem/214/E Relay Race time limit per test4 secondsmemory l ... 
随机推荐
- Appium+python自动化2-环境搭建(下)
			上一篇android测试开发环境已经准备好, 接下来就是appium的环境安装了.环境安装过程中切勿浮躁,按照步骤一个个来. 环境装好后,可以用真机连电脑,也可以用android-sdk里面的模拟器( ... 
- 人工智能AI芯片与Maker创意接轨 (上)
			近几年来人工智能(Artificial Intelligence, AI)喴的震天价响,吃也要AI,穿也要AI,连上个厕所也要来个AI智能健康分析,生活周遭食衣住行育乐几乎无处不AI,彷佛已经来到科幻 ... 
- Linux命令的那些事(一)
			回顾一下前文,三大主流操作系统 windows做的最好(更准确最早做图形化界面是windows)其实是图形化界面占有90%的市场份额(PC(个人电脑)机的市场)但是现在发展图形界面做的较好其实Unix ... 
- 廖雪峰git教程学习笔记3
			commit是一串不便记忆的数字,为了方便记忆,引入tag,tag就跟HEAD一样,就像一个指针,指向commit,且指向是不能变得,一个commit就有一个tag 给当前分支下的当前commit打上 ... 
- Plasma Cash 合约解读
			作者介绍 虫洞社区·签约作者 steven bai Plasma Cash 合约解读 Plasma Cash 合约解读 1. 合约代码 2. 合约文件简单介绍 3. Plasma Cash 的基础数据 ... 
- 20135316王剑桥 linux第三周课实验笔记
			通过使用标准的字符码能够对文档中的字母和符号进行编码. 三种重要的数字表现形式: 1. 无符号数:编码基于传统的二进制表示法表示大于或等于零的数字. 2. 补码:编码是表示有符号整数的最常见方法,可以 ... 
- selenium之鼠标事件
			1.鼠标悬停火狐版本51,selenium版本3ActionChains(driver).move_to_element(above).perform()执行代码时,报错:selenium.commo ... 
- flownet2.0 caffe anaconda2 编译安装
			1. 下载flownet2.0源码到指定目录 cd /home/zzq/saliency_models/deep_optical_flow git clone https://github.com/l ... 
- 牛客网国庆集训派对Day4题目 2018年
			链接:https://www.nowcoder.com/acm/contest/204/A来源:牛客网 深度学习 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 1048576K,其他 ... 
- bootstrap使用总结
			bootstrap是一个webcss框架,集合了html/css/jquery为一家,创建响应式的页面.所谓的响应式就是适配不同的上网设备. 使用bootstrap的步骤: 1.下载bootstrap ... 
