题目描述

L 公司有 $n$ 个工厂,由高到低分布在一座山上,工厂 $1$ 在山顶,工厂 $n$ 在山脚。
由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在露天,以节省费用。突然有一天,L 公司的总裁 L 先生接到气象部门的电话,被告知三天之后将有一场暴雨,于是 L 先生决定紧急在某些工厂建立一些仓库以免产品被淋坏。
由于地形的不同,在不同工厂建立仓库的费用可能是不同的。第 $i$ 个工厂目前已有成品 $p_i$​ 件,在第 $i$ 个工厂位置建立仓库的费用是 $c_i$。
对于没有建立仓库的工厂,其产品应被运往其他的仓库进行储藏,而由于 L 公司产品的对外销售处设置在山脚的工厂 $n$,故产品只能往山下运(即只能运往编号更大的工厂的仓库),当然运送产品也是需要费用的,一件产品运送一个单位距离的费用是 $1$。
假设建立的仓库容量都都是足够大的,可以容下所有的产品。你将得到以下数据:

  1. 工厂 $i$ 距离工厂 $1$ 的距离 $x_i$​(其中 $x_1=0$)。
  2. 工厂 $i$ 目前已有成品数量 $p_i$。
  3. 在工厂 $i$ 建立仓库的费用 $c_i$。

请你帮助 L 公司寻找一个仓库建设的方案,使得总的费用(建造费用 + 运输费用)最小。

输入格式

输入的第一行是一个整数 $n$,代表工厂的个数。
第 $2$ 到 $(n + 1)$ 行,每行有三个用空格隔开的整数,第 $(i + 1)$ 行的整数依次代表 $x_i,~p_i,~c_i$。

输出格式

仅输出一行一个整数,代表最优方案的费用。

输入输出样例

输入 #1

3
0 5 10
5 3 100
9 6 10

输出 #1

32

说明/提示

样例输入输出 $1$ 解释

在工厂 $1$ 和工厂 $3$ 建立仓库,建立费用为 $10+10=20$ ,运输费用为 $(9-5) \times 3 = 12$,总费用 $32$。

数据范围与约定

对于 $20\%$ 的数据,保证 $n \leq 500$。
对于 $40\%$ 的数据,保证 $n \leq 10^4$。
对于 $100\%$ 的数据,保证 $1 \leq n \leq 10^6$,$0 \leq x_i,~p_i,~c_i < 2^{31}$。
对于任意的 $1 \leq i < n$,保证 $x_i < x_{i + 1}$。
设答案为 $ans$,保证 $ans + \sum\limits_{i = 1}^{n} p_ix_i < 2^{63}$。

思路

这是一道斜率优化DP的题,若不了解的请自行百度。直接放推导过程:

\[\begin{aligned} f_i&=\min_{0\le j<i}\{f_j+\sum_{j<k<i}(x_i-x_k)\times p_k\}+c_i\\ &=\min_{0\le j<i}\{f_j+\sum_{1<k<i}(x_i-x_k)\times p_k-\sum_{1\le k\le j}(x_i-x_k)\times p_k\}+c_i\\ &=\min_{0\le j<i}\{f_j+\sum_{1\le k<i}x_ip_k-\sum_{1\le k<i}x_kp_k-\sum_{1\le k\le j}x_ip_k+\sum_{1\le k\le j}x_kp_k\}+c_i\\ &=\min_{0\le j<i}\{f_j+x_i\sum_{1\le k<i}p_k-\sum_{1\le k<i}x_kp_k-x_i\sum_{1\le k\le j}p_k+\sum_{1\le k\le j}x_kp_k\}+c_i\\ &=\min_{0\le j<i}\{f_j+x_i(\sum_{1\le k<i}p_k-\sum_{1\le k\le j}p_k)-\sum_{1\le k<i}x_kp_k+\sum_{1\le k\le j}x_kp_k\}+c_i\\ &\ \ \ \ \ (a_i:=\sum_{1\le j\le i}p_j,\ \ b_i:=\sum_{1\le j\le i}x_jp_j)\\ &=\min_{0\le j<i}\{f_j+x_i(a_{i-1}-a_j)-b_{i-1}+b_j\}+c_i\\ \end{aligned}\]

把 $i$ 当做常量,移项:

\[\begin{aligned} f_i&=\min_{0\le j<i}\{f_j+x_i(a_{i-1}-a_j)-b_{i-1}+b_j\}+c_i\\ f_i-x_ia_{i-1}+b_{i-1}-c_i&=\min_{0\le j<i}\{f_j-x_ia_j+b_j\} \end{aligned}\]

设 $0\le k<j<i$, 且 $j$ 比 $k$ 优, 则有:

\[\begin{aligned} &\begin{aligned} f_j-x_ia_j+b_j&\le f_k-x_ia_k+b_k\\ x_ia_k-x_ia_j&\le (f_k+b_k)-(f_j+b_j)\\ x_i(a_k-a_j)&\le (f_k+b_k)-(f_j+b_j)\\ \end{aligned}\\ &\begin{aligned} &\because a\nearrow,k<j\\ &\therefore a_k<a_j\\ &\therefore a_k-a_j<0\\ &\therefore x_i\ge \frac{(f_k+b_k)-(f_j+b_j)}{a_k-a_j}\\ \end{aligned}\\ \end{aligned}\]

设 $P_i(a_i,f_i+b_i)$ 为平面直角坐标系上的点, 那么 $\frac{(f_k+b_k)-(f_j+b_j)}{a_k-a_j}$ 就是直线 $y_{k,j}$ 的斜率。当这个斜率小于等于 $x_i$ 时, $j$ 比 $k$ 更优,且该不等式符合任何一个更大的 $x_i$ ,所以可以将 $k$ 扔掉。

因为 $x$ 是单调递增的,所以我们要维护一个下凸包

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1000005;
int n,x[MAXN],p[MAXN],c[MAXN],a[MAXN],b[MAXN],f[MAXN];
int q[MAXN],head=1,tail=1;
inline long double slope(int j,int k) { return (long double)(f[k]+b[k]-f[j]-b[j])/(a[k]-a[j]); }
signed main() {
    scanf("%lld", &n);
    for(int i=1;i<=n;i++) scanf("%lld%lld%lld", &x[i], &p[i], &c[i]);
    for(int i=1;i<=n;i++) a[i]=a[i-1]+p[i],b[i]=b[i-1]+x[i]*p[i];
    for(int i=1;i<=n;i++) {
        while(head<tail && slope(q[head], q[head+1]) <= x[i]) head++;
        int j=q[head];f[i]=f[j]+x[i]*(a[i-1]-a[j])-b[i-1]+b[j]+c[i];
        while(head<tail && slope(q[tail-1], i)<=slope(q[tail-1], q[tail])) tail--;
        q[++tail]=i;
    }
    printf("%lld\n", f[n]);
    return 0;
}