斜率优化

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

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

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

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

我们得到类似于 \(a[i] \times 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 \right)\) ,斜率为 \(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;
}
posted @ 2020-11-18 12:12  nao-nao  阅读(90)  评论(0编辑  收藏  举报