線段樹做法,拿下你谷最劣解。
題意翻譯很形象,就不說了。
思路
最大化最小值,我們很容易想到二分答案。很容易發現,答案具有單調性。
我們二分一個答案 \(x\) ,強制每次使用的區間長度都不小於 \(x\) ,然後判斷可行性。
現在問題轉化為怎麼判斷一個答案 \(x\) 是否可行。
我們發現,如果列舉左端點 \(l_i\) ,右端點的取值是在一個範圍內的,即 \(r_i\in[p_i,n]\) ,然後 \(p_i\) 我們可以透過二分快速計算出來。不難發現 \(p_i\) 也是單調遞增的。
現在問題轉化為,可以選若干個區間 \([l,r]\) ,將區間內的所有數都減 \(1\) ,問最終是否能使所有數變為 \(0\) 。
區間減 \(1\) 比較難維護,考慮問題的等價轉化,轉成差分陣列,我們引入 \(d\) 陣列,令 \(d_i=b_i-b_{i-1}\) ,所有數為 \(0\) 等價於 \(\forall i\in[1,n],d_i=0\) ,為了程式寫的方便,我們引入點 \(x_{n+1}=inf\) ,並且規定 \(d_{n+1}\) 取任意數均可。
然後問題又轉化成,可以在 \([1,l_i]\) 中選一個 \(d_x\) 將其減一,在 \([p_i+1,n]\) 中選一個 \(d_x\) 使其加一。
我們從 \(1\) 到 \(n\) 遍歷 ,如果遇見某一位是負數,那麼就是非法的,所以有一個比較明顯的貪心策略:
- 從 \(1\) 到 \(n\) 遍歷 \(d_i\) ,如果 \(d_i<0\) 返回情況非法,否則,貪心的選擇 \(j\in[p+1,n]\) 內當前的數和最左邊的負數 \(d_j\),使其變為正數,並在 \(d_i\ge 0\) 的條件下。
貪心正確性顯然,交換證明即可,可自己嘗試。
最後,使用線段樹動態維護最左邊的負數即可。當然,使用 multiset
也可,或者一個佇列維護,但是不太想寫了。
使用線段樹和二分,所以時間複雜度 \(O(n\log n\log w)\),強勢拿下最劣解。
code
#include<bits/stdc++.h>
#define mp make_pair
#define N 200005
#define ll long long
#define Pr pair<ll,int>
#define fi first
#define se second
using namespace std;
const ll inf=1e10;
int n;
ll a[N],b[N],t[N];
ll d[N];
struct segtree{
Pr tr[N*4];
void Pushup(int x)
{
if(tr[x*2].fi>=0) tr[x]=tr[x*2+1];
else tr[x]=tr[x*2];
}
void modify(int l,int r,int P,int x,ll v)
{
if(l==r)
{
tr[x]=mp(v,l);
return;
}
int mid=(l+r)/2;
if(P<=mid) modify(l,mid,P,x*2,v);
else modify(mid+1,r,P,x*2+1,v);
Pushup(x);
}
Pr query(int l,int r,int L,int R,int x)
{
if(l>R||r<L) return mp(inf,-1);
if(l>=L&&r<=R) return tr[x];
int mid=(l+r)/2;
Pr lv=query(l,mid,L,R,x*2),rv=query(mid+1,r,L,R,x*2+1);
if(lv.fi>=0) return rv;
else return lv;
}
}tr;
bool check(ll x)
{
memcpy(b,t,sizeof t);
for(int i=1;i<=n;i++) tr.modify(1,n,i,1,b[i]);
for(int i=1;i<=n;i++)
{
if(b[i]<0) return 0;
int p=upper_bound(a,a+1+n,a[i-1]+1+x)-a;// p->n
while(b[i])
{
Pr t=tr.query(1,n,p,n,1);
ll mv=t.first;
int mp=t.second;
if(mv>=0) break;
if(mv+b[i]>0) b[i]+=mv,b[mp]=0,tr.modify(1,n,mp,1,0),tr.modify(1,n,i,1,b[i]);
else
{
b[mp]+=b[i];
b[i]=0;
tr.modify(1,n,mp,1,b[mp]);
tr.modify(1,n,i,1,0);
break;
}
}
}
return 1;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n;i++) scanf("%lld",&t[i]);
for(int i=n;i>=1;i--) t[i]-=t[i-1];
a[0]=-inf,a[n+1]=inf;
ll l=1,r=1e9+5;
while(l<=r)
{
ll mid=(l+r)/2;
if(check(mid)) l=mid+1;
else r=mid-1;
}
if(l>1e9) printf("-1");
else printf("%lld",l-1);
return 0;
}