P5017题解

前言

做这道题,首先要了解 dpdp 一般有三个步骤(个人理解):

  1. 根据题意确定状态。
  2. 根据状态的定义推出状态转移方程,一般有两种:填表法和刷表法。填表法就是普通 dp,用前面的状态转移到现在的状态,例:f[i]=f[i1]+a[i]。刷表法就是在现有的基础上(f[i] 已知),去推出 f[k]k>i),例:f[i+1]=f[i]+a[i]
  3. 处理边界并输出。边界问题是 dp 中非常重要的部分,且每道题的边界不同,要视题意而定。

注:此题采用刷表法。


暴力

对于每道题,首先考虑暴力。那么这道题呢,首先你必须想到的就是需要排序,毕竟排序后车子在每个时间段接的人你才能知道。排完序后,试着用 f[i] 表示前 i 个人都被接走得最小等待时间。

试着推状态转移方程:f[k]=f[i]+T(k>i),那么 T 等于 i+1 ~ k 的总等待时间,这里补充一下 T 怎么算:T=l(ki)(t[k]t[i])lk 被接走的时间,t 是前缀和数组,这个公式不理解没关系,请继续看下去(这是 dp 的状态转移方程,在正解里我会解释)。因为我们要用到 l,但是我们并不知道 k 是什么时候被接走的(也就是不知道 l),所以我们就无法算出 T

当我们发现一维不行时,就定义二维状态转移方程。根据一维的推导过程中发现,是因为我们不知道 k 是什么时候被接走的,才失败的。所以第二维果断定义为时间:f[i][j] 表示前 i 个人在 j 时刻全被接走。那么我们现在就可以算出来 f[i][j] 了。但是我们发现第二维是 4106,空间和时间都爆了,但是可以拿 50 分,不推荐。

正解

我们现在的问题是第二维,那么我们考虑优化第二维:细心观察数据发现,n,m 都比较小,所以用他们来定义状态转移方程。再多想一点,我们发现:f[i][jm] 以下的状态都对 f[i][j] 这个状态没有影响。为什么呢?原因很简单,f[i][jm] 这个状态是随着前一次车子走的,而 f[i][j] 是随着这一次的车子走的(车子 m 分钟来回一次)。

所以我们不妨优化定义:f[i][j] 表示第 i 个人等了 j 分钟后前 i 个人全上车的最小等待时间。

那么状态转移方程就是:f[k][l]=f[i][j]+T。其中 l 表示第 k 个人的等候时间。T 表示 i+1 ~ k 这段区间内所有人的等候时间总和。所以我们现在的问题就变成了求 lT

  1. 先看 ll 表示的是第 k 个人的等候时间,且由 i转移而来,所以 l 等于 i 的上车时间加上车子的往返时间减去自己到达的时间(这里一定要弄懂),i 的等候时间就是 a[i]+ja 数组表示 i 到达的时间,ji 等待时间,所以 l=a[i]+j+ma[k],其中 a[i]+j+m 就是车子在送完 i 后回到起点的时间,用这个时间减去 k 到达的时间,不就是 k的等候时间吗?

  2. 再看TT 表示 i~k 这段区间内所有人的等候时间总和。这个也很好想,考虑一个事实,=。比如:结束时间为 5 ,有两个人,他们到达时间为 3,4,最后的等待时间就是 52(3+4)=3。那么这道题的结束时间就是 a[k]+l,就是到达时间(a[k])加上等候时间(l),人数就是(ki),他们等待时间之和可以用前缀和算出来,就是 t[k]t[i]t 是前缀和数组。那么这道题也就完了呢。

  3. 状态转移方程:f[k][l]=min(f[k][l],f[i][j]+(a[k]+l)(ki)+(t[k]t[i]))

  4. 边界问题:先初始化为了正无穷,f[0][0]0

  5. 细节问题看代码。

AC code

#include<bits/stdc++.h>
#define int long long
#define x first
#define y second
using namespace std;
typedef pair<int,int> pii;
const int N=510,M=110;
int inf;
int n,m;
int f[N][M];
int a[N],t[N];
signed main(){
    memset(f,0x3f,sizeof f);
    inf=f[0][0],cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    sort(a+1,a+n+1);
    for(int i=1;i<=n;i++) t[i]=t[i-1]+a[i];
    f[0][0]=0,a[0]=-inf;
    for(int i=0;i<=n;i++){
        int M=min(m-1,a[i+1]-a[i]);
		//优化时间和空间,j只用枚举到m-1,因为前面的数会跟着前面的车走
		//a[i+1]-a[i]表示这个点到下一个点的时间差,因为一旦与下一个点的距离不大于m-1,就可以同时乘坐同一次车 
        for(int j=0;j<=M;j++){
            if(f[i][j]==inf) continue; 
            for(int k=i+1;k<=n;k++){
                int l=max(a[i]+j+m-a[k],0ll);//等待时间 
                f[k][l]=min(f[k][l],f[i][j]+(a[k]+l)*(k-i)-(t[k]-t[i]));
                //状态转移方程式 
            }
        }
    }
    int ans=inf;
    for(int i=0;i<m;i++) ans=min(ans,f[n][i]);//最后取值 
    cout<<ans<<endl;
    return 0;
}/*
f[i][j]表示前i个人等了j时刻上车时的最小等候时间(滚动数组,降低时间与空间复杂度)
f[k][l]=min(f[k][l],f[i][j]+(t[k]+l)*(k-i)-(t[k]-t[i]));
*/

Thanks

posted @   Celestial_cyan  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示