用圖像斜率可以將一些問題由O(n2)降到O(n)的算法。
下面結合一道經典題目介紹斜率優化
(洛谷鏈接)[HNOI2008]玩具裝箱
這是一道典型的斜率優化題目。題目不再贅述,直接說與斜率優化相關的部分。
設爲填裝前個玩具需要的最小花費,爲前個物品的總長度。那麼可以得到:
如果直接算的話複雜度爲O(n2),會超時的,所以需要優化。
我們將這個式子進行整理和移項:
爲了方便觀察,我們令
現在這個式子變爲
其中只與有關,只與有關。
由於我們求的是,而的相關信息是之前已經知道的,若把看做,把看做,我們便得到下面這個直線方程:
這樣,的信息就被表示成了點。也就是說,我們要從當前個左右的點中找到一個點,使得最小,這個點就是點。
(圖:綠線是邊界,黃線是目標直線,其斜率爲)
這個方程的斜率是,最小時也就是這條直線在軸上的截距最小時,(和高中學過的線性規劃類似)我們把一條斜率的直線從最下面開始慢慢向上平移,這條直線第一個碰到的點就是點。
顯然,可能是點的點都在邊界上。又因爲是隨着i增長的,於是我們可以用單調隊列求:
(用slope(,)表示兩個點的斜率)
1.對head: while(slope(head,head+1)<2a) head++;
2.求出:f[i]=y+a^2-2ax
3.對tail: while(slope(tail-1,tail)>slope(tail-1,i)) tail--;
4.點入隊
對於第1步,理由是是遞增的,此時斜率小於的兩點的前一點一定不會是。
對於第3步,理由是若slope(tail-1,tail)>slope(tail-1,),那麼tail點會被tail-1和點包圍,這樣tail就不再可能是。
編程具體細節還會有需要注意的地方,但由於跟算法無關,在此就不說明了。
#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)的改進:
2. tail-- 部分及 head++ 部分可用二分查找優化。
3. 注意 1. 中的 (xu-xv)>0 和 (xi-xj)>0