斜率最佳化(李超樹)

和蜀玩發表於2024-06-26

不會單調佇列,又看到古神拿李超樹切斜率最佳化特別順手,遂來學了一下,確實挺好用。

李超樹

一種用來快速維護有關線段的資訊的資料結構,一般是 \(O(n\mathtt{log}^{\mathbf{2}}n)\) 的,在斜率最佳化題中用到的操作是 \(O(n\mathtt{log}n)\) 的(沒有斜率最佳化題會卡 \(O(n\mathtt{log}n)\) 吧)。
原理是遞迴來判斷線段在一段區間內的優劣,這個在做題時不用理會。只需要記住板子,然後把斜截式方程代到裡面,按具體題目微調一下,然後就可以了。OIer只需要求出 dp 方程,轉化成斜截式,代到李超樹裡就行了,可是李超樹要考慮的事情就比較多了。

拿一道P5785 任務安排的程式碼當例子,按這個模板寫就行。

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls t[now].l
#define rs t[now].r
const ll N=3*114514,M=1919810,inf=1145141919810;
ll n,s,ti[N],f[N],dp[N],id[N];
ll rt,cnt;
struct xx{
	ll val,id;
}a[N];
bool cmp(xx x,xx y){
	return x.val<y.val;
}
struct tree{
	ll k,b;
	ll l,r;
}t[4*N];
ll get(ll id,ll x){
	return t[id].k*x+t[id].b;
}
void pushdown(ll &now,ll l,ll r,ll k,ll b){
	if(!now) now=++cnt; //這裡不一定
	ll mid=(l+r)>>1;
	ll la=get(now,a[l].val),ra=get(now,a[r].val);
	ll lx=k*a[l].val+b,rx=k*a[r].val+b;
	ll mid1=get(now,a[mid].val),mid2=k*a[mid].val+b;
	if(la<=lx&&ra<=rx) return;
	if(la>lx&&ra>rx){
		t[now].k=k,t[now].b=b;
		return;
	}
	if(mid1>mid2){
		swap(la,lx),swap(ra,rx);
		swap(t[now].k,k),swap(t[now].b,b);
	}
	if(la>lx) pushdown(ls,l,mid,k,b);
	if(ra>rx) pushdown(rs,mid+1,r,k,b);
}
ll query(ll now,ll l,ll r,ll tar){
	if(!now) return inf;
	ll mid=(l+r)>>1,ans=get(now,a[tar].val);
	if(tar<=mid) ans=min(ans,query(ls,l,mid,tar));
	else ans=min(ans,query(rs,mid+1,r,tar));
	return ans;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>s;
	for(int i=1;i<=n;++i){
		cin>>ti[i]>>f[i];
		ti[i]+=ti[i-1],f[i]+=f[i-1];
		a[i].val=ti[i]+s,a[i].id=i;
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;++i) id[a[i].id]=i;
	for(int i=1;i<=n;++i){
		dp[i]=query(rt,1,n,id[i])+f[n]*s+ti[i]*f[i];
		pushdown(rt,1,n,-f[i],dp[i]);
	}
	cout<<dp[n];
	return 0;
}

斜率最佳化

題一般都是能很簡單求出 \(O(n^{\mathbf{2]})\) 的轉移式,但是複雜度要求在 \(O(n\mathtt{log}n)\) 以內的,這個時候可以考慮斜率最佳化。因為使用的是李超樹,所以斜截式就要列成 \(y=kx+b\) 的形式。一般來說,只與 \(i\) 相關的項放到 \(y\),至於 \(j\) 相關的項和常數項放在 \(b\),同時和 \(i,j\) 相關的放在 \(kx\)。然後有些題會有些特殊操作,需要微調。

鴿

相關文章