1. 程式人生 > 實用技巧 >斜率優化--P3195 [HNOI2008]玩具裝箱

斜率優化--P3195 [HNOI2008]玩具裝箱

斜率優化的基本形式

對於這樣形式的\(dp\)方程:\(dp_i=Min/Max(a_i\times b_j+c_j+d_i)\),其中\(b\)嚴格單調遞增。

該方程的關鍵點在於\(a_i\times b_j\)這一項,它既有\(i\)又有\(j\),於是單調佇列優化不再適用,可以嘗試使用斜率優化。

代數理解

因為感覺影象理解並不是很嚴謹,所以推一下式子,比如這道題 P3195 [HNOI2008]玩具裝箱:

轉移方程:\(f_i = min(f_j+{(sum_i-sum_j+i-j-1-L)}^2)\) 既然是講斜率優化,狀態轉移應該就不用說了把

我們把這一系列的變數分類,把與\(i\)有關和與\(j\)

有關的變數分開,\(A = sum_i+i\)\(B = sum_j+j+L+1\)

那麼原式子就轉化為\(f_i = f_j+{(A-B)}^2 = f_j+A^2+B^2-2AB\) (因為每一個\(i\)只會由一個最優的\(j\)轉移過來,所以我們可以先把\(min\)拿掉)

對於兩個\(j\)\(1\leq j_1 < j_2\leq n\),如果\(f_{j_2}\)\(f_{j_1}\)更優,那麼:

\(f_{j_1}+A^2+{B_1}^2-2AB_1 > f_{j_2}+A^2+{B_2}^2-2AB_2\),變形後可得:\(\frac{(f_{j_2} + {B_2}^2) - (f_{j_1}+{B_1}^2)}{B_2-B_1} < 2A_i\)

可以發現他們兩兩形式相同,那麼我們就可以改寫成\(\frac{Y_2-Y_1}{X_2-X_1} < k\)

我們發現當\(j_1\)\(j_2\)練成的直線斜率小於\(k\),那麼選後面的點\(j_2\),那麼對答案產生貢獻的就是第一個斜率大於\(k\)的前面的點(這好像只能畫圖理解了,我並不會說明呀)

code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
int read(){
	int x = 1,a = 0;char ch = getchar();
	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
	return x*a;
}
const int maxn = 5e4+10;
int n,l;
double sum[maxn],A[maxn],B[maxn],c[maxn];
double dp[maxn],q[maxn];
double X(int x){return B[x];}
double Y(int x){return dp[x]+B[x]*B[x];}
double solve(int a,int b){return (Y(a)-Y(b))/(X(a)-X(b));}
int main(){
	n = read(),l = read();
	for (int i = 1;i <= n;i++) scanf("%lf",&c[i]),sum[i] = sum[i-1] + c[i];
	for (int i = 1;i <= n;i++) A[i] = sum[i]+i,B[i] = sum[i]+i+l+1;
	B[0] = l+1;
	int head = 1,tail = 1;
	for (int i = 1;i <= n;i++){
		while (head < tail&&solve(q[head],q[head+1]) < 2*A[i]) head++;
		int j = q[head];dp[i] = dp[j]+(A[i]-B[j])*(A[i]-B[j]);
		while(head < tail&&solve(i,q[tail-1]) < solve(q[tail-1],q[tail])) tail--;
		q[++tail]=i;
	}
	printf("%lld\n",(long long)dp[n]);
	return 0;
}