【BZOJ】1096: [ZJOI2007]仓库建设(dp+斜率优化)

时间:2023-03-09 16:52:56
【BZOJ】1096: [ZJOI2007]仓库建设(dp+斜率优化)

http://www.lydsy.com/JudgeOnline/problem.php?id=1096

首先得到dp方程(我竟然自己都每推出了QAQ)$$d[i]=min\{d[j]+cost(j+1,i)\}+c[i]$$其中$d[i]$是前i个且在i建仓库的最小费用,$cost(j+1,i)$是将j+1~i的东西全都运到i的费用

而我们先考虑cost怎么求,orz,好神的前缀和,首先维护sum[i]表示1~i的物品,则j~i的东西从j全都运到i需要$$(sum[i]-sum[j]) \times x[i]$$,然后再设前缀和b[i]表示1~i的物品每一个i从1运到i的费用,则$$cost[j+1,i]=(sum[i]-sum[j]) \times x[i]-(b[i]-b[j])$$其中$$sum[i]=sum[i-1]+p[i], b[i]=b[i-1]+p[i] \times x[i]$$

然后方程变成$$d[i]=min\{d[j]+(sum[i]-sum[j]) \times x[i]-(b[i]-b[j])\}+c[i]$$化简一下可得$$d[i]=min\{d[j]+b[j]-x[i] \times sum[j])\}+c[i]+sum[i] \times x[i]-b[i]$$

而对于这种方程,显然斜率优化。

而斜率优化有几个技巧:

1、不用1D1D直接上斜率,用通俗易懂的方法:

假设决策j<k且k比j优,即:

$$d[k]+b[k]-x[i] \times sum[k]<d[j]+b[j]-x[i] \times sum[j]$$

化简成:

$$\frac{d[k]+b[k]-d[j]-b[j]}{sum[k]-sum[j]}<x[i]$$

因为x[i]单调递增,所以当任意i<L,满足上边不等式,即决策k比j优(在i情况下k比j优的情况时),任意i<L的决策总是大于等于k的,所以当i取最优决策时,i<L的决策单调不降

接下来就是坐标轴的x单调递增,即根据上式,我们可以将点看做(sum[k], d[k]-b[k]),所以可以得到那个斜率方程,这样显然作为坐标轴x的sum[k]是单调不降的,所以得证。

因此这些决策就是二维平面上的点集,想象斜率为x[i]的直线从负无穷向正无穷扫,碰到的第一个点就是最优点(及凸包上的点),理由很简单,因为我们有上边那个不等式,凸包上的点k到任意点j<k的斜率总小于其它不在凸包上的点到任意点的斜率,即k为最优。

那么就维护一个单调队列,斜率递增,然后向求凸包的算法一样在队尾维护凸包即可。

2、论文《1D1D动态规划优化初步》上的做法(其实1的做法是这种做法的简便形式)

【BZOJ】1096: [ZJOI2007]仓库建设(dp+斜率优化)

而之前的某题:【BZOJ】1010: [HNOI2008]玩具装箱toy(dp+斜率优化)

【BZOJ】1096: [ZJOI2007]仓库建设(dp+斜率优化)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <map>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
#define mkpii make_pair<int, int>
#define pdi pair<double, int>
#define mkpdi make_pair<double, int>
#define pli pair<ll, int>
#define mkpli make_pair<ll, int>
#define rep(i, n) for(int i=0; i<(n); ++i)
#define for1(i,a,n) for(int i=(a);i<=(n);++i)
#define for2(i,a,n) for(int i=(a);i<(n);++i)
#define for3(i,a,n) for(int i=(a);i>=(n);--i)
#define for4(i,a,n) for(int i=(a);i>(n);--i)
#define CC(i,a) memset(i,a,sizeof(i))
#define read(a) a=getint()
#define print(a) printf("%d", a)
#define dbg(x) cout << (#x) << " = " << (x) << endl
#define error(x) (!(x)?puts("error"):0)
#define printarr2(a, b, c) for1(_, 1, b) { for1(__, 1, c) cout << a[_][__]; cout << endl; }
#define printarr1(a, b) for1(_, 1, b) cout << a[_] << '\t'; cout << endl
inline const int getint() { int r=0, k=1; char c=getchar(); for(; c<'0'||c>'9'; c=getchar()) if(c=='-') k=-1; for(; c>='0'&&c<='9'; c=getchar()) r=r*10+c-'0'; return k*r; }
inline const int max(const int &a, const int &b) { return a>b?a:b; }
inline const int min(const int &a, const int &b) { return a<b?a:b; } const int N=1000005;
int n, q[N], front, tail;
ll x[N], p[N], c[N], sum[N], b[N], f[N];
#define F(x) (f[x]+b[x])
#define E(k,j) (F(k)-F(j))
#define S(k,j) (sum[k]-sum[j]) int main() {
read(n);
for1(i, 1, n) read(x[i]), read(p[i]), read(c[i]);
for1(i, 1, n) sum[i]=sum[i-1]+p[i];
for1(i, 1, n) b[i]=b[i-1]+p[i]*x[i];
tail=1;
for1(i, 1, n) {
while(front<tail-1 && E(q[front+1], q[front])<x[i]*S(q[front+1], q[front])) ++front;
int j=q[front];
f[i]=f[j]+(sum[i]-sum[j])*x[i]-(b[i]-b[j])+c[i];
while(front<tail-1 && E(q[tail-1], q[tail-2])*S(i, q[tail-1])>E(i, q[tail-1])*S(q[tail-1], q[tail-2])) --tail;
q[tail++]=i;
}
printf("%lld", f[n]);
return 0;
}

  


Description

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

Input

第一行包含一个整数N,表示工厂的个数。接下来N行每行包含两个整数Xi, Pi, Ci, 意义如题中所述。

Output

仅包含一个整数,为可以找到最优方案的费用。

Sample Input

3
0 5 10
5 3 100
9 6 10

Sample Output

32

HINT

在工厂1和工厂3建立仓库,建立费用为10+10=20,运输费用为(9-5)*3 = 12,总费用32。如果仅在工厂3建立仓库,建立费用为10,运输费用为(9-0)*5+(9-5)*3=57,总费用67,不如前者优。

【数据规模】

对于100%的数据, N ≤1000000。 所有的Xi, Pi, Ci均在32位带符号整数以内,保证中间计算结果不超过64位带符号整数。

Source