斜率优化

感谢全机房唯一女装大佬 little-uu 的讲解

对于斜率优化,妙极秒极,所以我是没办法讲 /摊手

对于类似于 (f[i] = f[j] + a[i] imes b[j] + dots) 之类的式子可以使用斜率优化

首先想办法将式子里的变量的下表全变成 i 或 j ,然后将 (a[i] imes b[j]) 形式的式子放到一边,只和 i 有关的式子也放到这一边,只跟 j 有关的式子放到另一边。

我们得到类似于 (a[i] imes b[j] + f[i] + dots = f[j] + dots) 的式子。

我们考虑一次函数 (y = kx + b)

将其中 (a[i]) 视为 (k) ,(b[j]) 视为 (x) , (f[i] + dots) 视为 (b) , (f[j] + dots) 视为 (y)

我们可以得到对于此直线,它经过 (left(b[j],f[j]+dots ight)) ,斜率为 (a[i]) ,(f[i]) 只跟截距有关。

对于已经处理好的 (f[j]) 来说,都可以确定一个点,而对于需要计算的 (f[i]) 来说,都可以确定这条直线的斜率,两者结合可以求出一次函数截距。于是我们可以将问题转化。

我们现在有一些点,需要 求出在特定斜率下的截距最值,可以知道可以对答案造成贡献的只能是凸包上的点,使用栈来维护凸包。对于 (a[i]) 单调增的题目来说,可以使用双端队列来进行均摊 (O(1)) 的转移,否则需要在凸包上二分。

例题:P2120 [ZJOI2007]仓库建设

#include <cstdio>
#include <cstring>
#include <algorithm>

#define j1 q[l]
#define j2 q[l+1]
#define int long long

using namespace std;

int read()
{
	int a = 0,x = 1;char ch = getchar();
	while(ch > '9' || ch < '0') {if(ch == '-') x = -1;ch = getchar();}
	while(ch >= '0' && ch <= '9') {a = a*10 + ch-'0';ch = getchar();}
	return a*x;
}
const int N=1e6+7;
int n,x[N],p[N],c[N];
int a[N],b[N],A[N],f[N];
int q[N],l=1,r=1;

inline bool check(int a1,int a2,int a3)
{
	return (b[a3]+f[a3]-b[a1]-f[a1])*(a[a3]-a[a2]) > (b[a3]+f[a3]-b[a2]-f[a2])*(a[a3]-a[a1]);
}

signed main()
{
	n = read();
	for(int i = 1;i <= n;i ++) {
		x[i] = read(),p[i] = read(),c[i] = read();
		a[i] = a[i-1]+p[i],b[i] = b[i-1]+x[i]*p[i];
		A[i] = x[i]*a[i];
	}
	for(int i = 1;i <= n;i ++) {
		while(l<r) {
			if((b[j1]+f[j1] - b[j2]-f[j2]) > x[i]*(a[j1]-a[j2])) l ++;
			else break;
		}
		#define j q[l]
		f[i] = f[j]+A[i]-x[i]*a[j]-b[i]+b[j]+c[i];
		while(l<r) {
			if(check(q[r-1],q[r],i)) r --;
			else break;
		}
		q[++r] = i;
	}
	printf("%lld",f[n]);
	return 0;
}
原文地址:https://www.cnblogs.com/nao-nao/p/13999080.html