【题目链接】
ybt 1607:【 例 2】任务安排 2
洛谷 P10979 任务安排 2
注:ybt1607中n最大达到
1
0
4
10^4
104,洛谷P10979中n最大达到
3
∗
1
0
5
3*10^5
3∗105,本题解统一认为n最大达到
3
∗
1
0
5
3*10^5
3∗105。
【题目考点】
1. 动态规划:斜率优化动规
2. 单调队列
数组实现单调队列代码操作:
操作 | 代码 |
---|---|
单调队列初始化 | int q[N], l = 1, r = 0 |
单调队列是否为空 | l > r (队列不空:l <= r ) |
取队头 | q[l] |
取队尾 | q[r] |
队尾入队 | q[++r] = a |
队头出队 | ++l |
队尾出队 | --r |
【解题思路】
与本题题面相同但问题规模较小的题目:
信息学奥赛一本通 1606:【 例 1】任务安排 1 | 洛谷 P2365 任务安排
本题相比上题只有n的数值范围扩大到了
3
∗
1
0
5
3*10^5
3∗105,因此理论上不能再使用
O
(
n
2
)
O(n^2)
O(n2)的算法,应该使用
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)或
O
(
n
)
O(n)
O(n)的算法。状态定义和状态转移方程分析过程见上题中的解法2。
以下介绍的就是动态规划的斜率优化算法:
1. 整理状态转移方程
状态定义:
d
p
i
dp_i
dpi:前i个任务的所有子段划分方案中,执行任务的费用 加上当前方案下每批任务的启动时间对后续任务产生的费用 加和最小的任务批次划分方案的费用。
第i个任务执行时间为
t
i
t_i
ti,其前缀和为
T
T
T。第i个任务的费用系数为为
c
i
c_i
ci,其前缀和为
C
C
C
状态转移方程:
d
p
i
=
m
i
n
{
d
p
j
+
T
i
⋅
(
C
i
−
C
j
)
+
s
⋅
(
C
n
−
C
j
)
}
,
0
≤
j
<
i
dp_i = min\{dp_j+T_i\cdot (C_i-C_j)+s\cdot (C_n-C_j)\}, 0\le j< i
dpi=min{dpj+Ti⋅(Ci−Cj)+s⋅(Cn−Cj)},0≤j<i
将与j有关以及无关的项分开写,得到:
d
p
i
=
m
i
n
{
d
p
j
−
(
T
i
+
s
)
⋅
C
j
+
T
i
⋅
C
i
+
s
⋅
C
n
}
,
0
≤
j
<
i
dp_i = min\{dp_j-(T_i+s)\cdot C_j+T_i\cdot C_i+s\cdot C_n\}, 0\le j<i
dpi=min{dpj−(Ti+s)⋅Cj+Ti⋅Ci+s⋅Cn},0≤j<i
2. 决策点
每个j的取值称为一个决策点。假设
d
p
j
1
<
d
p
j
2
dp_{j_1}<dp_{j_2}
dpj1<dpj2,也就是决策点
j
1
j_1
j1优于
j
2
j_2
j2,
则
d
p
j
1
−
(
T
i
+
s
)
⋅
C
j
1
+
T
i
⋅
C
i
+
s
⋅
C
n
<
d
p
j
2
−
(
T
i
+
s
)
⋅
C
j
2
+
T
i
⋅
C
i
+
s
⋅
C
n
dp_{j_1}-(T_i+s)\cdot C_{j_1}+T_i\cdot C_i+s\cdot C_n < dp_{j_2}-(T_i+s)\cdot C_{j_2}+T_i\cdot C_i+s\cdot C_n
dpj1−(Ti+s)⋅Cj1+Ti⋅Ci+s⋅Cn<dpj2−(Ti+s)⋅Cj2+Ti⋅Ci+s⋅Cn
d
p
j
1
−
(
T
i
+
s
)
⋅
C
j
1
<
d
p
j
2
−
(
T
i
+
s
)
⋅
C
j
2
dp_{j_1}-(T_i+s)\cdot C_{j_1}< dp_{j_2}-(T_i+s)\cdot C_{j_2}
dpj1−(Ti+s)⋅Cj1<dpj2−(Ti+s)⋅Cj2
(
T
i
+
s
)
⋅
(
C
j
2
−
C
j
1
)
<
d
p
j
2
−
d
p
j
1
(T_i+s)\cdot (C_{j_2}-C_{j_1})< dp_{j_2}-dp_{j_1}
(Ti+s)⋅(Cj2−Cj1)<dpj2−dpj1
如果已知
C
j
1
<
C
j
2
C_{j_1}<C_{j_2}
Cj1<Cj2,那么
d
p
j
2
−
d
p
j
1
C
j
2
−
C
j
1
>
T
i
+
s
\frac{dp_{j_2}-dp_{j_1}}{C_{j_2}-C_{j_1}} > T_i+s
Cj2−Cj1dpj2−dpj1>Ti+s
反过来,已知
C
j
1
<
C
j
2
C_{j_1}<C_{j_2}
Cj1<Cj2
如果
d
p
j
2
−
d
p
j
1
C
j
2
−
C
j
1
>
T
i
+
s
\frac{dp_{j_2}-dp_{j_1}}{C_{j_2}-C_{j_1}} > T_i+s
Cj2−Cj1dpj2−dpj1>Ti+s,那么决策点
j
1
j_1
j1优于
j
2
j_2
j2
如果
d
p
j
2
−
d
p
j
1
C
j
2
−
C
j
1
<
T
i
+
s
\frac{dp_{j_2}-dp_{j_1}}{C_{j_2}-C_{j_1}} < T_i+s
Cj2−Cj1dpj2−dpj1<Ti+s,那么决策点
j
2
j_2
j2优于
j
1
j_1
j1
3. 决策点的几何表示
对于每个j,都能得到一组数对
(
C
j
,
d
p
j
)
(C_j, dp_j)
(Cj,dpj),将这组数对看作平面直角坐标系中的一个点,称为第j点
(
x
j
,
y
j
)
(x_j,y_j)
(xj,yj),其横纵坐标分别为
x
j
=
C
j
,
y
j
=
d
p
j
x_j=C_j,y_j=dp_j
xj=Cj,yj=dpj。
那么
k
(
j
1
,
j
2
)
=
y
j
2
−
y
j
1
x
j
2
−
x
j
1
=
d
p
j
2
−
d
p
j
1
C
j
2
−
C
j
1
k(j_1,j_2)=\frac{y_{j_2}-y_{j_1}}{x_{j_2}-x_{j_1}}=\frac{dp_{j_2}-dp_{j_1}}{C_{j_2}-C_{j_1}}
k(j1,j2)=xj2−xj1yj2−yj1=Cj2−Cj1dpj2−dpj1就是第
j
1
j_1
j1点和第
j
2
j_2
j2点连线的斜率。
4. 转为线性规划问题
将状态转移方程
d
p
i
=
m
i
n
{
d
p
j
−
(
T
i
+
s
)
⋅
C
j
+
T
i
⋅
C
i
+
s
⋅
C
n
}
,
0
≤
j
<
i
dp_i = min\{dp_j-(T_i+s)\cdot C_j+T_i\cdot C_i+s\cdot C_n\}, 0\le j<i
dpi=min{dpj−(Ti+s)⋅Cj+Ti⋅Ci+s⋅Cn},0≤j<i
去掉min,
C
j
C_j
Cj替换为
x
x
x,
d
p
j
dp_j
dpj替换为y,得到:
d
p
i
=
y
−
(
T
i
+
s
)
x
+
T
i
⋅
C
i
+
s
⋅
C
n
dp_i=y-(T_i+s)x+T_i\cdot C_i+s\cdot C_n
dpi=y−(Ti+s)x+Ti⋅Ci+s⋅Cn
移项得:
y
=
(
T
i
+
s
)
x
+
d
p
i
−
T
i
⋅
C
i
−
s
⋅
C
n
y=(T_i+s)x+dp_i-T_i\cdot C_i-s\cdot C_n
y=(Ti+s)x+dpi−Ti⋅Ci−s⋅Cn
这是一条斜率为
T
i
+
s
T_i+s
Ti+s,截距为
d
p
i
−
T
i
⋅
C
i
−
s
⋅
C
n
dp_i-T_i\cdot C_i-s\cdot C_n
dpi−Ti⋅Ci−s⋅Cn的直线。
记直线斜率
K
=
T
i
+
s
K=T_i+s
K=Ti+s,截距
B
=
d
p
i
−
T
i
⋅
C
i
−
s
⋅
C
n
B=dp_i-T_i\cdot C_i-s\cdot C_n
B=dpi−Ti⋅Ci−s⋅Cn,该直线可以表示为
y
=
K
x
+
B
y=Kx+B
y=Kx+B
显然,该直线的斜率是固定的,截距中只有
d
p
i
dp_i
dpi是可以变化的,也就是说
B
B
B越小
d
p
i
dp_i
dpi越小。
原状态转移方程要完成的事是:在
0
≤
j
<
i
0\le j<i
0≤j<i范围内选择一个j使得
d
p
i
dp_i
dpi的值最小。
该问题等价于在
0
≤
j
<
i
0\le j<i
0≤j<i范围内选择一个决策点j,使得斜率固定的直线
y
=
K
x
+
B
y=Kx+B
y=Kx+B在经过点
(
C
j
,
d
p
j
)
(C_j, dp_j)
(Cj,dpj)时,截距B最小。
使截距B最小的决策点j,也就是使
d
p
i
dp_i
dpi最小的j,称为最优决策点。
5. 决策点的比较条件
决策点比较条件可以写为:
已知
C
j
1
<
C
j
2
C_{j_1}<C_{j_2}
Cj1<Cj2
- 如果 k ( j 1 , j 2 ) > K k(j_1,j_2) > K k(j1,j2)>K,那么决策点 j 1 j_1 j1优于 j 2 j_2 j2,也就是两点连线斜率大于直线斜率,左侧点更优。
- 如果 k ( j 1 , j 2 ) < K k(j_1,j_2) < K k(j1,j2)<K,那么决策点 j 2 j_2 j2优于 j 1 j_1 j1,也就是两点连线斜率小于直线斜率,右侧点更优。
图示:
k
(
j
1
,
j
2
)
<
K
k(j_1,j_2) < K
k(j1,j2)<K,所以
j
2
j_2
j2优于
j
1
j_1
j1
k
(
j
2
,
j
3
)
>
K
k(j_2,j_3) > K
k(j2,j3)>K,所以
j
2
j_2
j2优于
j
3
j_3
j3
6. 上凸点与下凸点
已知有 j 1 , j 2 , j 3 j_1,j_2,j_3 j1,j2,j3三个点满足 C j 1 < C j 2 < C j 3 C_{j_1}<C_{j_2}<C_{j_3} Cj1<Cj2<Cj3
- 如果 k ( j 1 , j 2 ) < k ( j 2 , j 3 ) k(j_1,j_2)<k(j_2,j_3) k(j1,j2)<k(j2,j3),称 j 2 j_2 j2为下凸点。
- 如果
k
(
j
1
,
j
2
)
>
k
(
j
2
,
j
3
)
k(j_1,j_2)>k(j_2,j_3)
k(j1,j2)>k(j2,j3),称
j
2
j_2
j2为上凸点。
在选最优决策点时可以忽略上凸点
证明:假设 j 2 j_2 j2是 j 1 , j 2 , j 3 j_1,j_2,j_3 j1,j2,j3三个点中的上凸点,
如果 k ( j 1 , j 2 ) > K k(j_1,j_2)>K k(j1,j2)>K,则决策点 j 1 j_1 j1优于 j 2 j_2 j2。
如果 K > k ( j 1 , j 2 ) > k ( j 2 , j 3 ) K>k(j_1,j_2)>k(j_2,j_3) K>k(j1,j2)>k(j2,j3),那么决策点 j 3 j_3 j3优于 j 2 j_2 j2。
如果 K = k ( j 1 , j 2 ) K=k(j_1,j_2) K=k(j1,j2),那么选 j 1 j_1 j1和选 j 2 j_2 j2是一样的,可以不选 j 2 j_2 j2。
图示:
k
(
j
1
,
j
2
)
<
k
(
j
2
,
j
3
)
k(j_1,j_2)<k(j_2,j_3)
k(j1,j2)<k(j2,j3),
j
2
j_2
j2为下凸点,
j
2
j_2
j2可能是最优决策点。
k
(
j
1
,
j
4
)
>
k
(
j
4
,
j
3
)
k(j_1,j_4)>k(j_4,j_3)
k(j1,j4)>k(j4,j3),
j
4
j_4
j4为上凸点,
j
4
j_4
j4不可能是最优决策点。
7. 下凸壳
决策点的点集中,只有下凸点有可能作为最优决策点,因此我们只需要维护所有下凸点构成的下凸壳。
下凸壳上的点按
C
j
C_j
Cj从小到大记为
q
l
,
q
l
+
1
,
.
.
.
,
q
r
q_l,q_{l+1},...,q_r
ql,ql+1,...,qr
由于所有决策点都是下凸点,
所以
k
(
q
l
,
q
l
+
1
)
<
k
(
q
l
+
1
,
q
l
+
2
)
<
.
.
.
<
k
(
q
r
−
1
,
q
r
)
k(q_l,q_{l+1})<k(q_{l+1},q_{l+2})<...<k(q_{r−1},q_r)
k(ql,ql+1)<k(ql+1,ql+2)<...<k(qr−1,qr)
相邻点连成的线段的斜率是单调递增的。因此使用单调队列维护下凸壳。
8. 决策点队头出队
随着队列q下标x增大,决策点编号
j
=
q
x
j=q_x
j=qx单调递增,
C
j
C_j
Cj也单调递增
本题中直线斜率
K
=
T
i
+
s
K=T_i+s
K=Ti+s随着i的增大是单调递增的。
因此如果当前选择决策点
j
j
j,对于横坐标小于
C
j
C_j
Cj的决策点,也就是队列中
j
j
j前面的决策点都不可能再成为最优决策点,因此不需要再维护了。
具体做法为:
不断比较
k
(
q
l
,
q
l
+
1
)
k(q_l,q_{l+1})
k(ql,ql+1)和
K
K
K的大小
- 只要 k ( q l , q l + 1 ) ≤ K k(q_l,q_{l+1})≤K k(ql,ql+1)≤K,则队头 q l q_l ql出队
- 直到队列里只有一个点或 k ( q l , q l + 1 ) > K k(q_l,q_{l+1})>K k(ql,ql+1)>K,此时 q l q_l ql为最优决策点。
将最优决策点
q
l
q_l
ql带入状态转移方程,得出新的状态
d
p
i
dp_i
dpi,以及新的决策点i。
图示:
k
(
q
1
,
q
2
)
≤
K
k(q_1,q_2)≤K
k(q1,q2)≤K,则队头
q
1
q_1
q1出队;
k
(
q
2
,
q
3
)
≤
K
k(q_2,q_3)≤K
k(q2,q3)≤K,则队头
q
2
q_2
q2出队;
k
(
q
3
,
q
4
)
>
K
k(q_3,q_4)>K
k(q3,q4)>K,队头
q
3
q_3
q3是最优决策点。
9. 决策点队尾入队
求出状态
d
p
i
dp_i
dpi后,就得到一个新的决策点第
i
i
i点,其坐标为
(
C
i
,
d
p
i
)
(C_i,dp_i)
(Ci,dpi)。我们需要将该点加入决策点集。
由于
C
i
C_i
Ci随着i的增大单调递增,因此新的决策点
i
i
i一定在下凸壳最后一个决策点
q
r
q_r
qr的右侧。
如果新增第
i
i
i点导致下凸壳上最后一个决策点
q
r
q_r
qr变为上凸点,则将
q
r
q_r
qr点去掉,直到不产生上凸点时,将第i点加入决策点集的下凸壳。
具体做法:
不断比较
k
(
q
r
,
i
)
k(q_r,i)
k(qr,i)和
k
(
q
r
−
1
,
q
r
)
k(q_{r−1},q_r)
k(qr−1,qr)的大小
- 只要 k ( q r − 1 , q r ) ≥ k ( q r , i ) k(q_{r−1},q_r)≥k(q_r,i) k(qr−1,qr)≥k(qr,i),则将队尾 q r q_r qr出队
- 直到队列中只有一个点或
k
(
q
r
−
1
,
q
r
)
k(q_{r−1},q_r)
k(qr−1,qr)<
k
(
q
r
,
i
)
k(q_r,i)
k(qr,i),此时将
i
i
i队尾入队。
图示:
k ( q 3 , q 4 ) ≥ k ( q 4 , i ) k(q_3,q_4)≥k(q_4,i) k(q3,q4)≥k(q4,i),则将队尾 q 4 q_4 q4出队。
k ( q 2 , q 3 ) < k ( q 3 , i ) k(q_2,q_3)<k(q_3,i) k(q2,q3)<k(q3,i),则将i队尾入队。
10. 十字相乘
在具体计算时,应避免使用实数除法导致精度丢失,应该使用十字相乘法,在整数域内进行计算和比较。
- 判断
k
(
q
l
,
q
l
+
1
)
≤
K
k(q_l,q_{l+1})≤K
k(ql,ql+1)≤K,即判断
d
p
q
l
+
1
−
d
p
q
l
C
q
l
+
1
−
C
q
l
≤
T
i
+
s
\dfrac{dp_{q_{l+1}}−dp_{q_l}}{C_{q_{l+1}}−C_{q_l}}≤T_i+s
Cql+1−Cqldpql+1−dpql≤Ti+s
等价于判断 d p q l + 1 − d p q l ≤ ( T i + s ) ( C q l + 1 − C q l ) dp_{q_{l+1}}−dp_{q_l}\le (T_i+s)(C_{q_{l+1}}-C_{q_l}) dpql+1−dpql≤(Ti+s)(Cql+1−Cql) - 判断
k
(
q
r
−
1
,
q
r
)
≥
k
(
q
r
,
i
)
k(q_{r−1},q_r)≥k(q_r,i)
k(qr−1,qr)≥k(qr,i),即判断
d
p
i
−
d
p
q
r
C
i
−
C
q
r
≤
d
p
q
r
−
d
p
q
r
−
1
C
q
r
−
C
q
r
−
1
\dfrac{dp_i−dp_{q_r}}{C_i−C_{q_r}}≤\dfrac{dp_{q_r}−dp_{q_{r−1}}}{C_{q_r}−C_{q_{r−1}}}
Ci−Cqrdpi−dpqr≤Cqr−Cqr−1dpqr−dpqr−1
等价于判断 ( d p i − d p q r ) ( C q r − C q r − 1 ) ≤ ( d p q r − d p q r − 1 ) ( C i − C q r ) (dp_i−dp_{q_r})(C_{q_r}−C_{q_{r−1}})\le (dp_{q_r}−dp_{q_{r−1}})(C_i−C_{q_r}) (dpi−dpqr)(Cqr−Cqr−1)≤(dpqr−dpqr−1)(Ci−Cqr)
11. 其它
初始时已知
d
p
0
=
0
dp_0 = 0
dp0=0,应该先把第0点加入单调队列
最终结果是
d
p
n
dp_n
dpn
单调队列判断队列是否为空:l > r
,判断队列不空:l <= r
,判断队列中元素个数是否大于1个l < r
。
注意dp数组要设为long long类型。
本问题经过斜率优化后,算法时间复杂度为
O
(
n
)
O(n)
O(n)
【题解代码】
解法1:斜率优化动规 O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
#define N 300005
long long n, s, t[N], c[N], T[N], C[N], dp[N];//dp[i]:前i个任务的所有子段划分方案中,执行任务的费用 加上当前方案下每批任务的启动时间对后续任务产生的费用加和 最小的划分方案的费用
int q[N], l = 1, r = 0;//单调队列
int main()
{
cin >> n >> s;
for(int i = 1; i <= n; ++i)
{
cin >> t[i] >> c[i];
T[i] = T[i-1]+t[i];//T:t的前缀和
C[i] = C[i-1]+c[i];//C:c的前缀和
}
q[++r] = 0;//已知dp[0] = 0,加入第0点:(C[0],dp[0])
for(int i = 1; i <= n; ++i)
{
while(l < r && dp[q[l+1]]-dp[q[l]] <= (T[i]+s)*(C[q[l+1]]-C[q[l]]))
++l;
dp[i] = dp[q[l]]-(T[i]+s)*C[q[l]]+T[i]*C[i]+s*C[n];
while(l < r && (dp[i]-dp[q[r]])*(C[q[r]]-C[q[r-1]]) <= (C[i]-C[q[r]])*(dp[q[r]]-dp[q[r-1]]))
--r;
q[++r] = i;
}
cout << dp[n];
return 0;
}