【题目描述】
L公司有N个工厂,由高到底分布在一座山上。如图所示,工厂1在山顶,工厂N在山脚。
由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在露天,以节省费用。
突然有一天,L公司的总裁L先生接到气象部门的电话,被告知三天之后将有一场暴雨,于是L先生决定紧急在某些工厂建立一些仓库以免产品被淋坏。
由于地形的不同,在不同工厂建立仓库的费用可能是不同的。
第i个工厂目前已有成品$ P_i $件,在第$ i $个工厂位置建立仓库的费用是$ C_i $。
对于没有建立仓库的工厂,其产品应被运往其他的仓库进行储藏,而由于L公司产品的对外销售处设 置在山脚的工厂N,故产品只能往山下运(即只能运往编号更大的工厂的仓库),
当然运送产品也是需要费用的,假设一件产品运送1个单位距离的费用是1。假设建立的仓库容量都都是足够大的,可以容下所有的产品。
你将得到以下数据: 1:工厂$ i $距离工厂1的距离$ X_i $(其中$ X_1 $=0); 2:工厂$ i $目前已有成品数量$ P_i $; 3:在工厂$ i $建立仓库的费用$ C_i $;
请你帮助L公司寻找一个仓库建设的方案,使得总的费用(建造费用+运输费用)最小。
【题目链接】
BZOJ 1096 仓库建设 【ZJOI 2007】
【解题思路】
这题很像一道另一道题目嘛,【CEOI 2004】锯木厂选址。
首先考虑一个暴力DP,设$ f[i] $表示只在前$ i $个工厂,第$ i $个工厂必须建造仓库的最小花费。
通过考虑上一个仓库的建造位置$ j $,可得,
$$ f[i] = min{\ f[j] + cost(j, i), \ j \in [0, i)} + c_i$$
其中$ cost(j, i) $表示将$ [j + 1, i] $这一段存入$ i $的花费。
如何尽可能快地计算这个花费呢?我们借助前缀和的思想。
设$ w_i $为$P_i$的前缀和。 如果假定这些产品都是从起点运到$ i $的,那么总花费就是$ (w_i - w_j)x_i $,但因为这些产品并非是从起点运过来的,所以对于每组产品$ t $可节省$x_tP_t$的费用,那么设$ d_i $为$ x_iP_i $的前缀和,则$ cost(j, i) = (w_i - w_j)x_i - (d_i - d_j)$,所以我们的转移方程就成了:
$$ f[i] = min{\ f[j] + (w_i - w_j)x_i - (d_i - d_j), \ j \in [0, i)} + c_i$$
接下来就要准备斜率优化咯,展开,尽量分离$i, j$,容易得到,
设决策$ j $的决策费用为$ F(j) $,则, $$ F(j) = h(i) + g(j) - w_jx_i $$ 其中, $$ h(i) = w_ix_i - d_i + c_i$$ $$ g(j) = f[j] + d_j $$
然后考虑对于决策$j_1 < j_2$,$F(j_2) < F(j_1)$的充要条件化为斜率形式为:
$$ Slope(j_1, j_2) = \frac {g(j_1) - g(j_2)} {w_{j_1} - w_{j_2}} < x_i$$
因为$ x_i $递增,所以维护 $ Slope(j_k, j_{k + 1}) > x_i $,最优决策取队首。
同时维护斜率单调增。
【AC代码】
注意边界。
#include <cstdio>
const int MAXN = 1000061;
typedef long long int64;
int n;
int x[MAXN + 1], a[MAXN + 1], c[MAXN + 1];
int64 w[MAXN + 1], d[MAXN + 1];
int64 f[MAXN + 1];
inline int64 g(int i){
return f[i] + d[i];
}
inline int64 h(int i){
return x[i] * w[i] - d[i] + c[i];
}
inline double slope(int i, int j){
return (double)(g(i) - g(j)) / (w[i] - w[j]);
}
inline void dp(){
static int Q[MAXN];
int l = 0, r = -1;
f[0] = 0, Q[++r] = 0;
for(int i = 1; i <= n; i++){
while(l < r && slope(Q[l], Q[l + 1]) < x[i]) l++;
int j = Q[l];
f[i] = h(i) + g(j) - x[i] * w[j];
while(l < r && slope(Q[r - 1], Q[r]) > slope(Q[r], i)) r--;
Q[++r] = i;
}
}
int main(){
// freopen("storage.in", "r", stdin), freopen("storage.out", "w", stdout);
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d%d%d", x + i, a + i, c + i);
w[0] = d[0] = 0;
for(int i = 1; i <= n; i++){
w[i] = w[i - 1] + a[i];
d[i] = d[i - 1] + (int64)x[i] * a[i];
}
dp();
printf("%lld\n", f[n]);
// fclose(stdin), fclose(stdout);
return 0;
}
就是这样啦