[NOIP2018 普及组] 摆渡车题解

题目链接

题意

给出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,2m1]

题解

线性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(ik)tk} , j ∈ [ i − 2 m + 1 , i − m ] j \in [i-2m+1,i-m] j[i2m+1,im]
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(ik)tk = i ∗ ∑ t k − ∑ k ∗ t k i*\sum t_k - \sum{k*t_k} itkktk

预处理两个前缀和数组:

  • 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<=iktk

时间复杂度可以优化到 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+m1,最后一班车最晚会出现在最后一个人等车的时刻之后 m − 1 m-1 m1 ,因为没有人等车时间会超过 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+m1]

100pt

我们从感性上认识,如果在 ( i − m , i ] (i-m,i] (im,i]区间内没有人乘车,那么在时刻 i i i 发车和时刻 i − m i-m im 发车是一样的, 即 d p [ i ] = d p [ i − m ] dp[i] = dp[i-m] dp[i]=dp[im]

观察状态转移方程 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(ik)tk} , j ∈ [ i − 2 m + 1 , i − m ] j \in [i-2m+1,i-m] j[i2m+1,im]
当在 ( i − m , i ] (i-m,i] (im,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(ik)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[i2m+1,im]
使 d p [ i ] dp[i] dp[i]最小的 j j j是不是一定是 i − m i-m im?不一定。举例来看,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] [km,k+m]区间内的点 i i i d p [ i ] dp[i] dp[i]时需要枚举 j j j,这样的 i i i最多有 n ∗ m n*m nm个,时间复杂度为 O ( n ∗ m 2 ) O(n*m^2) O(nm2), 其余的点 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+nm2)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值