arc166D 做題小計

g1ove發表於2024-04-20

線段樹做法,拿下你谷最劣解。

題意翻譯很形象,就不說了。

思路

最大化最小值,我們很容易想到二分答案。很容易發現,答案具有單調性。

我們二分一個答案 \(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;
}