斜率优化--P3195 [HNOI2008]玩具装箱

斜率优化的基本形式

对于这样形式的(dp)方程:(dp_i=Min/Max(a_i imes b_j+c_j+d_i)),其中(b)严格单调递增。

该方程的关键点在于(a_i imes 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)(1leq j_1 < j_2leq 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)的前面的点(这好像只能画图理解了,我并不会说明呀)

变形状态转移的方法

(dp_i=Min/Max(a_i imes b_j+c_j+d_i)),其中(i imes j)的一项里,含(i)的是斜率,含(j)的是(x),剩下右边含(j)的是(y),其余的是(b)

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
",(long long)dp[n]);
	return 0;
}
原文地址:https://www.cnblogs.com/little-uu/p/13999098.html