啦啦啦,这一篇是接上一篇的博客,上一篇是记忆化搜索,而这一篇是dp+前缀和小技巧
dp这种玄学做法我这种蒟蒻当然不是自己想出来的,参考https://blog.csdn.net/kkkksc03/article/details/84798228
首先定义f[i],表示在时间i已积累的最小等待时间,因为时间范围是<=4*10^6,所以不会炸。定义cnt[i]表示到i时刻时已经到达的人数(包括被车送走的),sum[i]表示到i时刻时到达车站的同学的时间的总和,然后就可推出转移方程:f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j])。f[j]指的是上一次发车的时间。咳,那么这个方程是怎么推出来呢?
cnt[i]-cnt[j]指的是从i到j中新加入的新童鞋,在j到i这段时间中新加入的会积累等待时间,假设加入了s1,s2,s3(时间)三位童鞋,积累的等待时间即为i-s1+i-s2+i-s3,等于i*(cnt[i]-cnt[j])-(sum[i]-sum[j])
代码中的一点也是相同的思想:f[i]=cnt[i]*i-sum[i]
剩下就是代码啦,里面也有详细注释
#include<bits/stdc++.h> using namespace std; int a[501],cnt[4000005],sum[4000005],f[4000005]; //f[i]指i时刻已经积累的所有人的等待时间和 int main() { int n,m; scanf("%d%d",&n,&m); int Time=0; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); cnt[a[i]]++;//在a[i]时间到达的人 sum[a[i]]+=a[i];//sum是前缀和数组 Time=max(Time,a[i]);//求出到达时间中的最大,加上m作为dp的上限 } for(int i=1;i<Time+m;i++) { cnt[i]+=cnt[i-1];//cnt[i]指的是到i时刻时,已经到达了的人数(包括送走的) sum[i]+=sum[i-1];//sum[i]代表第i时间到达车站的同学的时间的总和 } for(int i=0;i<Time+m;i++)//dp时间 //Time+m:最后一个人最晚送的时间小于Time+m(也就是上一班车刚好在最后一个人前发车,刚好把最后一个人落下) { if (i>=m&&cnt[i-m]==cnt[i])//到下一次发车时中途没有人来 { f[i]=f[i-m];//等待时间不变化,直接跳过即可 continue; } f[i]=cnt[i]*i-sum[i];//初始化,这样还没开第一班车 int tmp; tmp=max(i-2*m+1,0);//车的等待时间不会超过m,超过m还不如多开一次 for(int j=tmp;j<=i-m;j++) f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));//转移方程 } int ans=2147483647; for(int i=Time;i<Time+m;i++)//注意范围 ans=min(ans,f[i]); printf("%d",ans); return 0; }
呼!终于把这道题弄完了(rp++)