信息学奥赛一本通 1609:【例 4】Cats Transport | 洛谷 CF311B Cats Transport
【题目链接】
ybt 1609:【例 4】Cats Transport
洛谷 CF311B Cats Transport
【题目考点】
1. 动态规划:斜率优化动规
【解题思路】
解法1:设a点的前缀和
输入的
d
d
d序列是从
d
2
d_2
d2到
d
n
d_n
dn,共n-1个数字。人为设
d
1
=
0
d_1=0
d1=0。
设
d
d
d序列的前缀和数组
s
s
s,自然
s
1
=
0
s_1=0
s1=0。
思考:一只猫在玩完后,刚好有个饲养员走到这只猫所在的山,将它接走,那么这名饲养员应该从哪一时刻出发?
设
a
i
a_i
ai表示第
i
i
i只猫在时刻
t
i
t_i
ti被一名饲养员接走,接走它的饲养员的出发时刻。
从第1座山走到第
h
i
h_i
hi座山的距离为
∑
x
=
2
h
i
d
x
=
s
h
i
−
s
1
=
s
h
i
\sum_{x=2}^{h_i}d_x = s_{h_i}-s_1=s_{h_i}
∑x=2hidx=shi−s1=shi。由于饲养员的速度为1米每单位时间,那么从第1座山走到第
h
i
h_i
hi座山花费的时间为
s
h
i
s_{h_i}
shi,饲养员在
t
i
t_i
ti时刻到达第
h
i
h_i
hi座山,那么饲养员的出发时间
a
i
=
t
i
−
s
h
i
a_i = t_i-s_{h_i}
ai=ti−shi
将所有猫的
a
i
a_i
ai从小到大排序。
如果饲养员在
a
i
+
x
a_i+x
ai+x时刻出发,会在
t
i
+
x
t_i+x
ti+x时刻接到第
i
i
i只猫,此时第
i
i
i只猫等待了
x
x
x个单位的时间。
现在设想一个表示时间的数轴,将每只猫的
a
i
a_i
ai标在数轴上,将这样的点叫做
a
a
a类点。各饲养员的出发时刻标记在数轴上,记为
b
1
,
.
.
.
,
b
p
b_1,...,b_p
b1,...,bp,这样的点叫做
b
b
b类点。
设
b
l
b_l
bl和
b
r
b_r
br是相邻的两个
b
b
b类点,那么所有满足
b
l
<
a
i
≤
b
r
b_l< a_i \le b_r
bl<ai≤br的
a
a
a类点对应的猫都会被在
b
r
b_r
br时刻出发的饲养员接走,第
i
i
i只猫的等待时长为
b
r
−
a
i
b_r-a_i
br−ai。将猫的等待时间称为代价,因此也可以说
a
i
a_i
ai点贡献的代价为
b
r
−
a
i
b_r-a_i
br−ai。
要想使代价和最小,
b
b
b点一定选择要在某
a
a
a点上,而不会选择在两个
a
a
a点之间。
如果 b l < a x ≤ . . . ≤ a y < b r < a y + 1 b_l<a_x\le...\le a_y<b_r<a_{y+1} bl<ax≤...≤ay<br<ay+1,则 b l b_l bl和 b r b_r br之间 a a a点的代价和为: ∑ b l < a i ≤ b r ( b r − a i ) \underset{b_l< a_i \le b_r}\sum(b_r-a_i) bl<ai≤br∑(br−ai)
将 b r b_r br点调整到 a y a_y ay, b r b_r br减小,代价和 ∑ b l < a i ≤ b r ( b r − a i ) \underset{b_l< a_i \le b_r}\sum(b_r-a_i) bl<ai≤br∑(br−ai)会减小。
a
a
a点序列为
a
0
,
a
1
,
.
.
.
,
a
m
a_0,a_1, ..., a_m
a0,a1,...,am,人为添加一个点
a
0
=
0
a_0=0
a0=0
设
b
b
b点选择的位置为
a
b
1
,
a
b
2
,
.
.
.
,
a
b
p
a_{b_1}, a_{b_2},...,a_{b_p}
ab1,ab2,...,abp,设
b
0
=
0
b_0=0
b0=0。
最后一个选择的点
a
b
p
a_{b_p}
abp必然是
a
m
a_m
am,即
b
p
=
m
b_p=m
bp=m。
如果 b p < m b_p<m bp<m,则 a m a_m am对应的猫不会被饲养员接走。如果 b p > m bp>m bp>m,不如 b p = m bp=m bp=m代价更小。
可以设
a
a
a序列的前缀和
s
u
m
A
sumA
sumA,
s
u
m
A
i
=
∑
x
=
1
i
a
x
sumA_i = \sum_{x=1}^ia_x
sumAi=∑x=1iax
已知
a
l
<
a
i
≤
a
r
a_l < a_i \le a_r
al<ai≤ar范围内有
r
−
l
r-l
r−l个
a
a
a点,该范围内
a
a
a点的加和
∑
a
l
<
a
i
≤
a
r
a
i
=
s
u
m
A
r
−
s
u
m
A
l
\underset{a_l<a_i\le a_r}\sum a_i = sumA_r-sumA_l
al<ai≤ar∑ai=sumAr−sumAl。
满足
a
l
<
a
i
≤
a
r
a_l<a_i\le a_r
al<ai≤ar的
a
a
a点的代价加和为
∑
a
l
<
a
i
≤
a
r
(
a
r
−
a
i
)
=
(
r
−
l
)
a
r
−
(
s
u
m
A
r
−
s
u
m
A
l
)
\underset{a_l< a_i \le a_r}\sum(a_r-a_i)=(r-l)a_r-(sumA_r-sumA_l)
al<ai≤ar∑(ar−ai)=(r−l)ar−(sumAr−sumAl)
状态定义
- 阶段:前
j
j
j个
a
a
a点中选择
i
i
i个点
前 j j j个 a a a点对应的猫都应该被接走,因此必须选择最后一个 a a a点 a j a_j aj作为 b b b点 - 决策:选择哪个 a a a点作为下一个 b b b点。
- 策略:选择点的方案
- 策略集合:在前 j j j个 a a a点中选择 i i i个点的所有方案
- 条件:代价最小
- 统计量:代价
状态定义
d
p
i
,
j
dp_{i,j}
dpi,j:在前
j
j
j个
a
a
a点中选择
i
i
i个点,且一定选择
a
j
a_j
aj点的所有方案中,代价最小的方案的代价。
初始状态:
当
i
≥
j
i\ge j
i≥j时,可以选择每个
a
a
a点都作为
b
b
b点,代价
d
p
i
,
j
dp_{i,j}
dpi,j为0。所以
d
p
i
,
0
=
0
,
0
≤
i
≤
p
dp_{i,0}=0, 0\le i \le p
dpi,0=0,0≤i≤p。
当
i
<
j
i<j
i<j且
i
=
0
i=0
i=0时,有猫但没有饲养员,是不符合条件的,因此设
d
p
0
,
j
=
inf
,
0
<
j
≤
m
dp_{0,j}=\inf,0<j\le m
dp0,j=inf,0<j≤m
状态转移方程
- 策略集合:在前 j j j个 a a a点中选择 i i i个点,且选择 a j a_j aj点的所有方案
- 分割策略集合:由于第 i i i个选点一定选择 a j a_j aj点,那么根据第 i − 1 i-1 i−1个选择的点分割策略集合。
设选择的第
i
−
1
i-1
i−1个点为
a
h
a_h
ah,选择
a
a
a点的最小值为
a
0
a_0
a0,也就是没有上一个选点,前
i
−
1
i-1
i−1个饲养员不接猫。选择的
a
a
a点最大为
a
j
−
1
a_{j-1}
aj−1,所以
0
≤
h
<
j
0\le h < j
0≤h<j
要想在
a
0
∼
a
j
a_0\sim a_j
a0∼aj中选择
i
i
i个点且包括
a
j
a_j
aj,使代价最小。那么首先在
a
0
∼
a
h
a_0\sim a_h
a0∼ah中选择
i
−
1
i-1
i−1个点且包括
a
h
a_h
ah,可以获得的最小代价为
d
p
i
−
1
,
h
dp_{i-1,h}
dpi−1,h。
a
h
a_h
ah的下一个选择的点为
a
j
a_j
aj,
a
h
<
a
x
≤
a
j
a_h<a_x\le a_j
ah<ax≤aj中的
a
x
a_x
ax产生代价
∑
x
=
h
+
1
j
(
a
j
−
a
x
)
=
(
j
−
h
)
a
j
−
(
s
u
m
A
j
−
s
u
m
A
h
)
=
(
j
−
h
)
a
j
+
s
u
m
A
h
−
s
u
m
A
j
\sum_{x=h+1}^j(a_j-a_x)=(j-h)a_j-(sumA_j-sumA_h)=(j-h)a_j+sumA_h-sumA_j
∑x=h+1j(aj−ax)=(j−h)aj−(sumAj−sumAh)=(j−h)aj+sumAh−sumAj。
对于不同的
h
h
h的取值,求代价的最小值。
因此状态转移方程为: d p i , j = m i n { d p i − 1 , h + ( j − h ) a j + s u m A h − s u m A j } , 0 ≤ h < j dp_{i,j}=min\{dp_{i-1,h}+(j-h)a_j+sumA_h-sumA_j\},0\le h < j dpi,j=min{dpi−1,h+(j−h)aj+sumAh−sumAj},0≤h<j
进行斜率优化:
方程中去掉
m
i
n
min
min,将与
h
h
h相关的当做变量,与整理方程
−
d
p
i
−
1
,
h
−
s
u
m
A
h
=
j
⋅
a
j
−
h
⋅
a
j
−
d
p
i
,
j
−
s
u
m
A
j
-dp_{i-1,h}-sumA_h=j\cdot a_j-h\cdot a_j-dp_{i,j}-sumA_j
−dpi−1,h−sumAh=j⋅aj−h⋅aj−dpi,j−sumAj
d
p
i
−
1
,
h
+
s
u
m
A
h
=
a
j
⋅
h
+
d
p
i
,
j
−
j
⋅
a
j
+
s
u
m
A
j
dp_{i-1,h}+sumA_h=a_j\cdot h+dp_{i,j}-j\cdot a_j+sumA_j
dpi−1,h+sumAh=aj⋅h+dpi,j−j⋅aj+sumAj
设
y
=
d
p
i
−
1
,
h
+
s
u
m
A
h
y=dp_{i-1,h}+sumA_h
y=dpi−1,h+sumAh,
x
=
h
x=h
x=h,
k
=
a
j
k=a_j
k=aj,
b
=
d
p
i
,
j
−
j
⋅
a
j
+
s
u
m
A
j
b=dp_{i,j}-j\cdot a_j+sumA_j
b=dpi,j−j⋅aj+sumAj
则该方程就是直线方程
y
=
k
x
+
b
y=kx+b
y=kx+b,决策点为
(
h
,
d
p
i
−
1
,
h
+
s
u
m
A
h
)
(h, dp_{i-1,h}+sumA_h)
(h,dpi−1,h+sumAh),看直线过哪个决策点时截距
b
b
b最小,就可以求出
d
p
i
,
j
dp_{i,j}
dpi,j。
斜率
a
j
a_j
aj随着
j
j
j的增大而增大,因此可以进行队头出队,取队头即为最优决策点。
斜率优化的具体原理、和代码细节不再赘述,参考模板题:
信息学奥赛一本通 1607:【 例 2】任务安排 2 | 洛谷 P10979 任务安排 2
求出的 p p p个饲养员接 m m m只猫的最小代价 d p p , m dp_{p,m} dpp,m即为最终结果
解法2:最后减 a a a点加和
a
a
a点序列为
a
0
,
a
1
,
.
.
.
,
a
m
a_0,a_1, ..., a_m
a0,a1,...,am,人为添加一个点
a
0
=
0
a_0=0
a0=0
设
b
b
b点选择的位置为
a
b
1
,
a
b
2
,
.
.
.
,
a
b
p
a_{b_1}, a_{b_2},...,a_{b_p}
ab1,ab2,...,abp,设
b
0
=
0
b_0=0
b0=0。
最后一个选择的点
a
b
p
a_{b_p}
abp必然是
a
m
a_m
am,即
b
p
=
m
b_p=m
bp=m。
已知
a
l
<
a
i
≤
a
r
a_l < a_i \le a_r
al<ai≤ar范围内有
r
−
l
r-l
r−l个
a
a
a点。
- 满足 a b 0 < a i ≤ a b 1 a_{b_0}<a_i\le a_{b_1} ab0<ai≤ab1的 a a a点的代价加和为 ∑ a b 0 < a i ≤ a b 1 ( a b 1 − a i ) = b 1 a b 1 − ∑ 0 < a i ≤ a b 1 a i \underset{a_{b_0}< a_i \le a_{b_1}}\sum(a_{b_1}-a_i)=b_1a_{b_1}-\underset{0< a_i \le a_{b_1}}\sum a_i ab0<ai≤ab1∑(ab1−ai)=b1ab1−0<ai≤ab1∑ai
- 满足 a b 1 < a i ≤ a b 2 a_{b_1}<a_i\le a_{b_2} ab1<ai≤ab2的 a a a点的代价加和为 ∑ a b 1 < a i ≤ a b 2 ( a b 2 − a i ) = ( b 2 − b 1 ) a b 2 − ∑ a b 1 < a i ≤ a b 2 a i \underset{a_{b_1}< a_i \le a_{b_2}}\sum(a_{b_2}-a_i)=(b_2-b_1)a_{b_2}-\underset{a_{b_1}< a_i \le a_{b_2}}\sum a_i ab1<ai≤ab2∑(ab2−ai)=(b2−b1)ab2−ab1<ai≤ab2∑ai
- …
- 满足
a
b
p
−
1
<
a
i
≤
a
b
p
a_{b_{p-1}}<a_i\le a_{b_p}
abp−1<ai≤abp的
a
a
a点的代价加和为
∑
a
b
p
−
1
<
a
i
≤
a
b
p
(
a
b
p
−
a
i
)
=
(
b
p
−
b
p
−
1
)
a
b
p
−
∑
a
b
p
−
1
<
a
i
≤
a
b
p
a
i
\underset{a_{b_{p-1}}< a_i \le a_{b_p}}\sum(a_{b_p}-a_i)=(b_p-b_{p-1})a_{b_p}-\underset{a_{b_{p-1}}< a_i \le a_{b_p}}\sum a_i
abp−1<ai≤abp∑(abp−ai)=(bp−bp−1)abp−abp−1<ai≤abp∑ai
总代价和为:
∑ i = 1 p ( b i − b i − 1 ) a b i − ∑ i = 1 b p a i \sum_{i=1}^p(b_i-b_{i-1})a_{b_i}-\sum_{i=1}^{b_p}a_i ∑i=1p(bi−bi−1)abi−∑i=1bpai
后面 ∑ i = 1 b p a i = ∑ i = 1 m a i \sum_{i=1}^{b_p}a_i=\sum_{i=1}^ma_i ∑i=1bpai=∑i=1mai是定值,记为 s u m A sumA sumA,在动规过程中不用考虑。
只需要考虑不同选择点的方案中,哪种方案的代价 ∑ i = 1 p ( b i − b i − 1 ) a b i \sum_{i=1}^p(b_i-b_{i-1})a_{b_i} ∑i=1p(bi−bi−1)abi是最小的。对于相邻选择的点 a b i − 1 a_{b_{i-1}} abi−1和 a b i a_{b_i} abi,贡献的代价为 ( b i − b i − 1 ) a b i (b_i-b_{i-1})a_{b_i} (bi−bi−1)abi。
状态定义
状态定义
d
p
i
,
j
dp_{i,j}
dpi,j:在前
j
j
j个
a
a
a点中选择
i
i
i个点,且一定选择
a
j
a_j
aj点的所有方案中,代价最小的方案的代价。
初始状态:
当
i
≥
j
i\ge j
i≥j时,可以选择每个
a
a
a点都作为
b
b
b点,代价
d
p
i
,
j
dp_{i,j}
dpi,j为0。所以
d
p
i
,
0
=
0
,
0
≤
i
≤
p
dp_{i,0}=0, 0\le i \le p
dpi,0=0,0≤i≤p。
当
i
<
j
i<j
i<j且
i
=
0
i=0
i=0时,有猫但没有饲养员,是不符合条件的,因此设
d
p
0
,
j
=
inf
,
0
<
j
≤
m
dp_{0,j}=\inf,0<j\le m
dp0,j=inf,0<j≤m
状态转移方程
- 策略集合:在前 j j j个 a a a点中选择 i i i个点,且选择 a j a_j aj点的所有方案
- 分割策略集合:由于第 i i i个选点一定选择 a j a_j aj点,那么根据第 i − 1 i-1 i−1个选择的点分割策略集合。
设选择的第
i
−
1
i-1
i−1个点为
a
h
a_h
ah,选择
a
a
a点的最小值为
a
0
a_0
a0,也就是没有上一个选点,前
i
−
1
i-1
i−1个饲养员不接猫。选择的
a
a
a点最大为
a
j
−
1
a_{j-1}
aj−1,所以
0
≤
h
<
j
0\le h < j
0≤h<j
要想在
a
0
∼
a
j
a_0\sim a_j
a0∼aj中选择
i
i
i个点且包括
a
j
a_j
aj,使代价最小。那么首先在
a
0
∼
a
h
a_0\sim a_h
a0∼ah中选择
i
−
1
i-1
i−1个点且包括
a
h
a_h
ah,可以获得的最小代价为
d
p
i
−
1
,
h
dp_{i-1,h}
dpi−1,h。
a
h
a_h
ah的下一个选择的点为
a
j
a_j
aj,
a
h
<
a
x
≤
a
j
a_h<a_x\le a_j
ah<ax≤aj中的
a
x
a_x
ax产生代价
(
j
−
h
)
a
j
(j-h)a_j
(j−h)aj。
对于不同的
h
h
h的取值,求代价的最小值。
因此状态转移方程为: d p i , j = m i n { d p i − 1 , h + ( j − h ) a j } , 0 ≤ h < j dp_{i,j}=min\{dp_{i-1,h}+(j-h)a_j\},0\le h < j dpi,j=min{dpi−1,h+(j−h)aj},0≤h<j
进行斜率优化:
方程中去掉
m
i
n
min
min,将与
h
h
h相关的当做变量,与整理方程
−
d
p
i
−
1
,
h
=
j
⋅
a
j
−
h
⋅
a
j
−
d
p
i
,
j
-dp_{i-1,h}=j\cdot a_j-h\cdot a_j-dp_{i,j}
−dpi−1,h=j⋅aj−h⋅aj−dpi,j
d
p
i
−
1
,
h
=
a
j
⋅
h
+
d
p
i
,
j
−
j
⋅
a
j
dp_{i-1,h}=a_j\cdot h+dp_{i,j}-j\cdot a_j
dpi−1,h=aj⋅h+dpi,j−j⋅aj
设
y
=
d
p
i
−
1
,
h
y=dp_{i-1,h}
y=dpi−1,h,
x
=
h
x=h
x=h,
k
=
a
j
k=a_j
k=aj,
b
=
d
p
i
,
j
−
j
⋅
a
j
b=dp_{i,j}-j\cdot a_j
b=dpi,j−j⋅aj
则该方程就是直线方程
y
=
k
x
+
b
y=kx+b
y=kx+b,决策点为
(
h
,
d
p
i
−
1
,
h
)
(h, dp_{i-1,h})
(h,dpi−1,h),看直线过哪个决策点时截距
b
b
b最小,就可以求出
d
p
i
,
j
dp_{i,j}
dpi,j。
斜率
a
j
a_j
aj随着
j
j
j的增大而增大,因此可以进行队头出队,取队头即为最优决策点。
求出的
p
p
p个饲养员接
m
m
m只猫的最小代价
d
p
p
,
m
dp_{p,m}
dpp,m,再减去所有
a
a
a点的加和
s
u
m
A
sumA
sumA,即为最终结果。
【题解代码】
解法1:斜率优化动规 一般解法 设a点的前缀和
#include <bits/stdc++.h>
using namespace std;
#define N 100005
#define INF 0x3f3f3f3f3f3f3f3f
typedef long long LL;
LL n, m, p, d[N], s[N], a[N], dp[105][N], sumA[N];
int q[N], l = 1, r = 0;
LL X(int h)
{
return h;
}
LL Y(int i, int h)
{
return dp[i-1][h]+sumA[h];
}
LL K(int j)
{
return a[j];
}
bool cmp(LL a1, LL b1, LL a2, LL b2)//a1/b1 <= a2/b2
{
return a1*b2 <= a2*b1;
}
int main()
{
int t, h;
cin >> n >> m >> p;
for(int i = 2; i <= n; ++i)
{
cin >> d[i];
s[i] = s[i-1]+d[i];//s:d数组的前缀和
}
for(int i = 1; i <= m; ++i)
{
cin >> h >> t;
a[i] = t-s[h];
}
sort(a+1, a+1+m);
for(int i = 1; i <= m; ++i)
sumA[i] = sumA[i-1]+a[i];//sumA:a数组的前缀和
memset(dp, 0x3f, sizeof(dp));
for(int i = 0; i <= p; ++i)
dp[i][0] = 0;
for(int i = 1; i <= p; ++i)//先求出i-1行再求第i行
{
l = 1, r = 0;//清空单调队列
for(int j = 1; j <= m; ++j)//0<=h<j
{
while(l < r && cmp(Y(i, j-1)-Y(i, q[r]), X(j-1)-X(q[r]), Y(i, q[r])-Y(i, q[r-1]), X(q[r])-X(q[r-1])))
r--;
q[++r] = j-1;
while(l < r && cmp(Y(i, q[l+1])-Y(i, q[l]), X(q[l+1])-X(q[l]), K(j), 1))
l++;
int h = q[l];
dp[i][j] = dp[i-1][h]+(j-h)*a[j]+sumA[h]-sumA[j];
}
}
cout << dp[p][m];
return 0;
}
解法2:最后减 a a a点加和
#include <bits/stdc++.h>
using namespace std;
#define N 100005
typedef long long LL;
LL n, m, p, d[N], s[N], a[N], dp[105][N], sumA;
int q[N*100], l = 1, r = 0;
LL X(int h)
{
return h;
}
LL Y(int i, int h)
{
return dp[i-1][h];
}
LL K(int j)
{
return a[j];
}
bool cmp(LL a1, LL b1, LL a2, LL b2)//a1/b1 <= a2/b2
{
return a1*b2 <= a2*b1;
}
int main()
{
int t, h;
cin >> n >> m >> p;
for(int i = 2; i <= n; ++i)
{
cin >> d[i];
s[i] = s[i-1]+d[i];//s:d数组的前缀和
}
for(int i = 1; i <= m; ++i)
{
cin >> h >> t;
a[i] = t-s[h];
sumA += a[i];
}
sort(a+1, a+1+m);
memset(dp, 0x3f, sizeof(dp));
for(int i = 0; i <= p; ++i)
dp[i][0] = 0;
for(int i = 1; i <= p; ++i)//先求出i-1行再求第i行
{
l = 1, r = 0;//清空单调队列
for(int j = 1; j <= m; ++j)//0<=h<j
{
while(l < r && cmp(Y(i, j-1)-Y(i, q[r]), X(j-1)-X(q[r]), Y(i, q[r])-Y(i, q[r-1]), X(q[r])-X(q[r-1])))
r--;
q[++r] = j-1;
while(l < r && cmp(Y(i, q[l+1])-Y(i, q[l]), X(q[l+1])-X(q[l]), K(j), 1))
l++;
dp[i][j] = dp[i-1][q[l]]+(j-q[l])*a[j];
}
}
cout << dp[p][m]-sumA;
return 0;
}