信息学奥赛一本通 1611:仓库建设 | 洛谷 P2120 [ZJOI2007] 仓库建设
【题目链接】
ybt 1611:仓库建设
洛谷 P2120 [ZJOI2007] 仓库建设
【题目考点】
1. 动态规划:斜率优化动规
【解题思路】
状态定义
- 阶段:将前i个工厂的产品都运进仓库
由于只能往编号更大的工厂运货,为了保证前i个工厂的货都进仓库,那么第i工厂必须建立仓库。 - 决策:在哪个工厂建立仓库
- 策略:选择工厂建立仓库的方案
- 策略集合:要想将前i个工厂的产品都运进仓库,建立仓库的方案
- 条件:费用最小
- 统计量:费用
状态定义
d
p
i
dp_i
dpi:考虑将前
i
i
i个工厂的产品都运进仓库,且一定在第
i
i
i工厂建立仓库的所有方案中,费用最小的方案的费用。
初始状态
d
p
0
=
0
dp_0=0
dp0=0
状态转移方程
- 策略集合:要想将前i个工厂的产品都运进仓库,建立仓库的方案
- 分割策略集合:由于最后一个仓库已经必须建在第 i i i工厂,那么根据倒数第二个仓库建立的位置分割策略集合。
设倒数第二个仓库建立在工厂 j j j, j j j最小可以为0,也就是不建立倒数第二个仓库,一共只在第 i i i工厂建一个仓库。 j j j最大可以为 i − 1 i-1 i−1,因此 0 ≤ j ≤ i − 1 0\le j \le i-1 0≤j≤i−1。
- 在第 j j j工厂建立仓库后,将前 j j j个工厂的产品都运进仓库,且在第 j j j工厂建立仓库的最小费用为 d p j dp_j dpj。
- 第
j
+
1
j+1
j+1工厂的产品需要运到第
i
i
i工厂的仓库,有
p
j
+
1
p_{j+1}
pj+1个产品,运送距离为
x
i
−
x
j
+
1
x_i-x_{j+1}
xi−xj+1,费用为
p
j
+
1
(
x
i
−
x
j
+
1
)
p_{j+1}(x_i-x_{j+1})
pj+1(xi−xj+1)
第 j + 2 j+2 j+2工厂的产品需要运到第 i i i工厂的仓库,有 p j + 2 p_{j+2} pj+2个产品,运送距离为 x i − x j + 2 x_i-x_{j+2} xi−xj+2,费用为 p j + 2 ( x i − x j + 2 ) p_{j+2}(x_i-x_{j+2}) pj+2(xi−xj+2)
。。。
第 i − 1 i-1 i−1工厂的产品需要运到第 i i i工厂的仓库,有 p i − 1 p_{i-1} pi−1个产品,运送距离为 x i − x i − 1 x_i-x_{i-1} xi−xi−1,费用为 p i − 1 ( x i − x i − 1 ) p_{i-1}(x_i-x_{i-1}) pi−1(xi−xi−1)
第 i i i工厂的产品需要运到第 i i i工厂的仓库,有 p i p_{i} pi个产品,运送距离为 x i − x i x_i-x_{i} xi−xi,费用为 p i ( x i − x i ) p_{i}(x_i-x_{i}) pi(xi−xi)
将第 j + 1 j+1 j+1工厂到第 i i i工厂的产品运送到第 i i i工厂的仓库的总费用为 ∑ k = j + 1 i p k ( x i − x k ) = ∑ k = j + 1 i x i p k − ∑ k = j + 1 i x k p k \sum_{k=j+1}^{i}p_k(x_i-x_k)=\sum_{k=j+1}^{i}x_ip_k-\sum_{k=j+1}^{i}x_kp_k ∑k=j+1ipk(xi−xk)=∑k=j+1ixipk−∑k=j+1ixkpk
= x i ∑ k = j + 1 i p k − ∑ k = j + 1 i x k p k =x_i\sum_{k=j+1}^ip_k-\sum_{k=j+1}^{i}x_kp_k =xi∑k=j+1ipk−∑k=j+1ixkpk
设 x k p k x_kp_k xkpk序列的前缀和为 s x p sxp sxp, p p p序列的前缀和为 s p sp sp,因此:
∑ k = j + 1 i p k ( x i − x k ) = x i ( s p i − s p j ) − ( s x p i − s x p j ) \sum_{k=j+1}^{i}p_k(x_i-x_k)=x_i(sp_i-sp_j)-(sxp_{i}-sxp_{j}) ∑k=j+1ipk(xi−xk)=xi(spi−spj)−(sxpi−sxpj) - 在第 i i i工厂建仓库需要费用 c i c_i ci。
- 对于 j j j的所有取整情况取最小值
综上,考虑将前
i
i
i个工厂的产品都运进仓库,且一定在第
i
i
i工厂建立仓库的最小的方案的费用
d
p
i
=
m
i
n
{
d
p
j
+
x
i
(
s
p
i
−
s
p
j
)
−
(
s
x
p
i
−
s
x
p
j
)
+
c
i
}
,
0
≤
j
≤
i
−
1
dp_i=min\{dp_j+x_i(sp_i-sp_j)-(sxp_{i}-sxp_{j})+c_i\},0\le j \le i-1
dpi=min{dpj+xi(spi−spj)−(sxpi−sxpj)+ci},0≤j≤i−1
接下来进行斜率优化动规,去掉min,将与
j
j
j相关的量视作遍历,与
i
i
i相关的量视作常量,整理方程
−
d
p
j
−
s
x
p
j
=
−
x
i
s
p
j
+
x
i
s
p
i
−
d
p
i
−
s
x
p
i
+
c
i
-dp_j-sxp_j=-x_isp_j+x_isp_i-dp_i-sxp_i+c_i
−dpj−sxpj=−xispj+xispi−dpi−sxpi+ci
d
p
j
+
s
x
p
j
=
x
i
s
p
j
+
d
p
i
+
s
x
p
i
−
x
i
s
p
i
−
c
i
dp_j+sxp_j=x_isp_j+dp_i+sxp_i-x_isp_i-c_i
dpj+sxpj=xispj+dpi+sxpi−xispi−ci
设
x
=
s
p
j
x=sp_j
x=spj,
y
=
d
p
j
+
s
x
p
j
y=dp_j+sxp_j
y=dpj+sxpj,
k
=
x
i
k=x_i
k=xi,
b
=
d
p
i
+
s
x
p
i
−
x
i
s
p
i
−
c
i
b=dp_i+sxp_i-x_isp_i-c_i
b=dpi+sxpi−xispi−ci
则以上方程就是直线方程
y
=
k
x
+
b
y=kx+b
y=kx+b,对于每个
j
j
j的取值,
(
s
p
j
,
d
p
j
+
s
x
p
j
)
(sp_j, dp_j+sxp_j)
(spj,dpj+sxpj)是一个决策点,直线的斜率是固定的,看该直线经过哪条决策点时直线的截距
b
b
b最小,此时
d
p
i
dp_i
dpi的值就是所求的值。
该题中直线斜率
x
i
x_i
xi随着
i
i
i的增大而增大,可以使用队头出队操作,取队头就是最优决策点。
斜率优化的具体原理和代码细节不再赘述,参考模板题:
信息学奥赛一本通 1607:【 例 2】任务安排 2 | 洛谷 P10979 任务安排 2
最后一个
p
i
>
0
p_i>0
pi>0的工厂为第
m
m
m工厂,所有编号大于
m
m
m的工厂都没有货物。因而最后一个仓库可以建在第
m
m
m到第
n
n
n工厂中的任意一个工厂,看在哪建仓库费用最小。
因此最终结果为
m
i
n
{
d
p
i
}
,
m
≤
i
≤
n
min\{dp_i\},m\le i \le n
min{dpi},m≤i≤n
【题解代码】
解法1:斜率优化动规
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define N 1000005
LL n, m, x[N], p[N], sp[N], sxp[N], c[N], dp[N], ans = 1e18;//dp[i]:考虑将前i个工厂的产品都运进仓库,且一定在第i工厂建立仓库的所有方案中,费用最小的方案的费用。
int q[N], l = 1, r = 0;//单调队列
LL X(int j)
{
return sp[j];
}
LL Y(int j)
{
return dp[j]+sxp[j];
}
LL K(int i)
{
return x[i];
}
bool cmp(LL a1, LL b1, LL a2, LL b2)//a1/b1 <= a2/b2
{
return a1*b2 <= a2*b1;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for(int i = 1; i <= n; ++i)
{
cin >> x[i] >> p[i] >> c[i];
sp[i] = sp[i-1]+p[i];
sxp[i] = sxp[i-1]+x[i]*p[i];
if(p[i] > 0)
m = i;//m:记录最后一个有产品的工厂
}
for(int i = 1; i <= n; ++i)
{
while(l < r && cmp(Y(i-1)-Y(q[r]), X(i-1)-X(q[r]), Y(q[r])-Y(q[r-1]), X(q[r])-X(q[r-1])))
r--;
q[++r] = i-1;
while(l < r && cmp(Y(q[l+1])-Y(q[l]), X(q[l+1])-X(q[l]), K(i), 1))
l++;
dp[i] = dp[q[l]]+x[i]*(sp[i]-sp[q[l]])-(sxp[i]-sxp[q[l]])+c[i];
if(i >= m)//求dp[m]~dp[n]中的最小值
ans = min(ans, dp[i]);
}
cout << ans;
return 0;
}