【算法介紹】斜率優化

用圖像斜率可以將一些問題由O(n2)降到O(n)的算法。

下面結合一道經典題目介紹斜率優化

(洛谷鏈接)[HNOI2008]玩具裝箱

這是一道典型的斜率優化題目。題目不再贅述,直接說與斜率優化相關的部分。

f[i]f[i]爲填裝前ii個玩具需要的最小花費,s[i]s[i]爲前ii個物品的總長度。那麼可以得到:
f[i]=minj=0i1{f[j]+(s[i]s[j]+ij1L)2}f[i]=min_{j=0}^{i-1}\{ f[j]+(s[i]-s[j]+i-j-1-L)^2 \}
如果直接算的話複雜度爲O(n2),會超時的,所以需要優化。

我們將這個式子進行整理和移項:
f[i]=f[j]+[(s[i]+i)(s[j]+j+1+L)]2f[i]=f[j]+[(s[i]+i)-(s[j]+j+1+L)]^2
f[i]=f[j]+(s[j]+j+1+L)2+(s[i]+i)22(s[i]+i)(s[j]+j+1+L)f[i]=f[j]+(s[j]+j+1+L)^2+(s[i]+i)^2-2(s[i]+i)(s[j]+j+1+L)
2(s[i]+i)(s[j]+j+1+L)+f[i](s[i]+i)2=f[j]+(s[j]+j+1+L)22(s[i]+i)(s[j]+j+1+L)+f[i]-(s[i]+i)^2=f[j]+(s[j]+j+1+L)^2
爲了方便觀察,我們令a=s[i]+i,b=s[j]+j+1+La=s[i]+i,b=s[j]+j+1+L
現在這個式子變爲
2ab+f[i]a2=f[j]+b22ab+f[i]-a^2=f[j]+b^2
其中aa只與ii有關,bb只與jj有關。
由於我們求的是f[i]f[i],而jj的相關信息是之前已經知道的,若把f[j]+b2f[j]+b^2看做yy,把bb看做xx,我們便得到下面這個直線方程:
y=2ax+f[i]a2y=2ax+f[i]-a^2
這樣,jj的信息就被表示成了點。也就是說,我們要從當前ii個左右的點中找到一個點,使得f[i]f[i]最小,這個點就是jj點。
點
(圖:綠線是邊界,黃線是目標直線,其斜率爲2a2a

這個方程的斜率是2a(2a>0)2a(2a>0)f[i]f[i]最小時也就是這條直線在yy軸上的截距最小時,(和高中學過的線性規劃類似)我們把一條斜率2a2a的直線從最下面開始慢慢向上平移,這條直線第一個碰到的點就是jj點。

顯然,可能是jj點的點都在邊界上。又因爲2a2a是隨着i增長的,於是我們可以用單調隊列求f[i]f[i]
(用slope(ii,jj)表示兩個點的斜率)

1.對head: while(slope(head,head+1)<2a) head++;
2.求出f[i]f[i]:f[i]=y+a^2-2ax
3.對tail: while(slope(tail-1,tail)>slope(tail-1,i)) tail--;
4.ii點入隊

對於第1步,理由是2a2a是遞增的,此時斜率小於2a2a的兩點的前一點一定不會是jj
對於第3步,理由是若slope(tail-1,tail)>slope(tail-1,ii),那麼tail點會被tail-1和ii點包圍,這樣tail就不再可能是jj

編程具體細節還會有需要注意的地方,但由於跟算法無關,在此就不說明了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;

#define a(i) (s[i]+i)
#define b(i) (s[i]+i+L+1)
#define x(i) b(i)
#define y(i) (f[i]+b(i)*b(i))
#define slope(i,j) (y(j)-y(i))*1.0/(x(j)-x(i))

const int N=5e4+10;
int n, L;
LL s[N], f[N];

int q[N], head, tail;

int main()
{
	scanf("%d%d", &n, &L);
	for(int i=1; i<=n; ++i)
	{
		scanf("%lld", s+i);
		s[i]+=s[i-1];
	}
	
	//
	head=tail=0;
	q[tail++]=0;
	
	for(int i=1; i<=n; ++i)
	{
		while(head+1<tail&&slope(q[head],q[head+1])<2*a(i)) head++;
		f[i]=y(q[head])+a(i)*a(i)-2*a(i)*b(q[head]);
		while(head+1<tail && slope(q[tail-2],q[tail-1])>slope(q[tail-2],i)) tail--;
		q[tail++]=i;
	}
	
	//
	printf("%lld\n", f[n]);
	return 0;
}

改進與注意
1. 對 slope(i,j)>slope(u,v)的改進:
(yiyj)(xuxv)>(yuyv)(xixj)(y_i-y_j)(x_u-x_v)>(y_u-y_v)(x_i-x_j)
2. tail-- 部分及 head++ 部分可用二分查找優化。
3. 注意 1. 中的 (xu-xv)>0 和 (xi-xj)>0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章