题意
给出n名同学到达车站的时间,只有一辆摆渡车,来回需要 m m m分钟,求将所有同学摆渡到终点,所有同学的最少等车时间之和。
抽象一下题意:
- 数轴:时间轴
- 点权值:此时刻等车的人数
- 区间右端点:发车时刻
- 区间内每个点到区间右端点的加权距离和:等车时间和
问题变成,将要数轴划分若干成左开右闭的区间,区间长度 ≥ m \geq m ≥m,每个区间的值定义为区间内每个点到区间右端点的加权距离和,求所有区间值和的最小值。
根据题意,易得区间长度 ≥ m \geq m ≥m
进一步分析,区间长度 < 2 m < 2m <2m,如果区间长度 ≥ 2 m \geq 2m ≥2m,即两次发车的时刻相差 ≥ 2 m \geq 2m ≥2m,那中间完全可以发一次车。
因此区间长度的范围为 : [ m , 2 m − 1 ] [m, 2m-1] [m,2m−1]
题解
线性DP+前缀和优化
70pt
定义状态 d p [ i ] dp[i] dp[i]为最后一个区间的右端为 i i i时的所有区间值和的最小值,即当最后一班发车时间为 i i i时的等车时间之和。
那么状态转移方程为:
d
p
[
i
]
=
m
i
n
{
d
p
[
j
]
+
∑
k
=
j
+
1
k
<
=
i
(
i
−
k
)
∗
t
k
}
dp[i] = min\{dp[j] + \sum_{k=j+1}^{k<=i} {(i-k)*t_k}\}
dp[i]=min{dp[j]+∑k=j+1k<=i(i−k)∗tk} ,
j
∈
[
i
−
2
m
+
1
,
i
−
m
]
j \in [i-2m+1,i-m]
j∈[i−2m+1,i−m]
j
j
j为上一个合法区间的右端点值,即上一次的可能的发车时刻,
t
k
t_k
tk表示在时刻
t
t
t等车的人数。
时间复杂度为 O ( m 2 t ) O(m^2t) O(m2t)
显然,状态转移方程中的求和部分可以前缀和优化:
∑
k
=
j
+
1
k
<
=
i
(
i
−
k
)
∗
t
k
\sum_{k=j+1}^{k<=i} {(i-k)*t_k}
∑k=j+1k<=i(i−k)∗tk =
i
∗
∑
t
k
−
∑
k
∗
t
k
i*\sum t_k - \sum{k*t_k}
i∗∑tk−∑k∗tk。
预处理两个前缀和数组:
- e x s u m [ i ] exsum[i] exsum[i] 表示 ∑ k = 0 k < = i t k \sum_{k=0}^{k<=i}t_k ∑k=0k<=itk
- e x m s [ i ] exms[i] exms[i] 表示 ∑ k = 0 k < = i k ∗ t k \sum_{k=0}^{k<=i}{k*t_k} ∑k=0k<=ik∗tk
时间复杂度可以优化到 O ( m t ) O(mt) O(mt)
【代码】
#include <bits/stdc++.h>
using namespace std;
const int MAXM = 110;
const int MAXN = 510;
const int MAXT = 4e6+10;
const int inf = 0x3fffffff;
int m,n,maxt;
int t[MAXT],dp[MAXT];
int exsum[MAXT],exms[MAXT];
int cal(){
memset(dp,0x3f,sizeof(dp));
for(int i = 0;i<m;i++) {
dp[i] = i*exsum[i] - exms[i];
}
for(int i = m;i<=maxt;i++){
for(int j = max(0,i-2*m+1);j<=i-m;j++){
int x =dp[j]+i*(exsum[i]-exsum[j])-(exms[i]-exms[j]);
dp[i] = x<dp[i]? x:dp[i];
}
}
int ans = inf;
for(int i = 0;i<m;i++) ans = min(ans,dp[maxt-i]);
return ans;
}
int main(){
scanf("%d%d",&n,&m);
int x;
for(int i = 0;i<n;i++){
scanf("%d",&x);
maxt = max(maxt,x);
t[x]++;
}
maxt += m-1;
exsum[0] = t[0];
exms[0] = 0*t[0];
for(int i = 1;i<=maxt;i++){
exsum[i] = exsum[i-1]+t[i];
exms[i] = exms[i-1] + i*t[i];
}
cout<<cal()<<endl;
return 0;
}
【注意几个细节】
- 边界处理
- 枚举到 m a x t + m − 1 maxt + m-1 maxt+m−1,最后一班车最晚会出现在最后一个人等车的时刻之后 m − 1 m-1 m−1 ,因为没有人等车时间会超过 m m m分钟。
- 最后的答案不是 d p [ m a x t ] dp[maxt] dp[maxt], 而是 m i n ( d p [ k ] ) , k ∈ [ m a x t , m a x t + m − 1 ] min(dp[k]), k\in[maxt,maxt+m-1] min(dp[k]),k∈[maxt,maxt+m−1]。
100pt
我们从感性上认识,如果在 ( i − m , i ] (i-m,i] (i−m,i]区间内没有人乘车,那么在时刻 i i i 发车和时刻 i − m i-m i−m 发车是一样的, 即 d p [ i ] = d p [ i − m ] dp[i] = dp[i-m] dp[i]=dp[i−m]。
观察状态转移方程
d
p
[
i
]
=
m
i
n
{
d
p
[
j
]
+
∑
k
=
j
+
1
k
<
=
i
(
i
−
k
)
∗
t
k
}
dp[i] = min\{dp[j] + \sum_{k=j+1}^{k<=i} {(i-k)*t_k}\}
dp[i]=min{dp[j]+∑k=j+1k<=i(i−k)∗tk} ,
j
∈
[
i
−
2
m
+
1
,
i
−
m
]
j \in [i-2m+1,i-m]
j∈[i−2m+1,i−m],
当在
(
i
−
m
,
i
]
(i-m,i]
(i−m,i]区间内没有人乘车时,
∑
k
=
j
+
1
k
<
=
i
(
i
−
k
)
∗
t
k
=
0
\sum_{k=j+1}^{k<=i} {(i-k)*t_k} = 0
∑k=j+1k<=i(i−k)∗tk=0,
则状态转移方程为:
d
p
[
i
]
=
m
i
n
{
d
p
[
j
]
}
dp[i] = min\{dp[j] \}
dp[i]=min{dp[j]} ,
j
∈
[
i
−
2
m
+
1
,
i
−
m
]
j \in [i-2m+1,i-m]
j∈[i−2m+1,i−m]。
使
d
p
[
i
]
dp[i]
dp[i]最小的
j
j
j是不是一定是
i
−
m
i-m
i−m?不一定。举例来看,m=3:
显然,对于
d
p
[
6
]
dp[6]
dp[6]而言,应该从
d
p
[
1
]
dp[1]
dp[1] 转移,而不是
d
p
[
3
]
dp[3]
dp[3]。
但是在向后转移的时候,从
d
p
[
5
]
dp[5]
dp[5]或
d
p
[
7
]
dp[7]
dp[7]转移肯定不劣于从
d
p
[
6
]
dp[6]
dp[6]转移,所以
d
p
[
6
]
dp[6]
dp[6]是无用的状态。
所以仍然可以将
d
p
[
6
]
dp[6]
dp[6]的值赋为
d
p
[
3
]
dp[3]
dp[3]。
【代码】
int cal(){
memset(dp,0x3f,sizeof(dp));
for(int i = 0;i<m;i++) {
dp[i] = i*exsum[i] - exms[i];
}
for(int i = m;i<=maxt;i++){
if(exsum[i] - exsum[i-m] == 0){
dp[i] = dp[i-m];
continue;
}
for(int j = max(0,i-2*m+1);j<=i-m;j++){
int x =dp[j]+i*(exsum[i]-exsum[j])-(exms[i]-exms[j]);
dp[i] = x<dp[i]? x:dp[i];
}
}
int ans = inf;
for(int i = 0;i<m;i++) ans = min(ans,dp[maxt-i]);
return ans;
}
【时间复杂度分析】
对于每一个有人等车的时刻
k
k
k,都会导致
[
k
−
m
,
k
+
m
]
[k-m,k+m]
[k−m,k+m]区间内的点
i
i
i求
d
p
[
i
]
dp[i]
dp[i]时需要枚举
j
j
j,这样的
i
i
i最多有
n
∗
m
n*m
n∗m个,时间复杂度为
O
(
n
∗
m
2
)
O(n*m^2)
O(n∗m2), 其余的点
i
i
i直接经过上述优化,
O
(
1
)
O(1)
O(1)可求
d
p
[
i
]
dp[i]
dp[i], 总时间复杂度为
O
(
t
+
n
∗
m
2
)
O(t+n*m^2)
O(t+n∗m2)