題目大意
有\(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 ;
}