【NOIP2018】摆渡车题解(DP)

题目:luogu5017.
题目大意:给定一辆车往返一趟的时间 m m m,并且车的容量无限大.现在要送 n n n个人,人 i i i会在时刻 t i ti ti到达车站,问车这 n n n个人等车时间之和最小是多少.

我们可以设 f [ i ] f[i] f[i]表示送前i个同学需要的最少等待时间,然后直接枚举从前面那个状态转移过来,可以做到 O ( n 2 ) O(n^2) O(n2).

但是很明显这个算法是不满足最优子结构的,因为总等待时间最短不一定代表回到起点的时间点最早.

我们考虑先预处理两个数组 c [ i ] c[i] c[i] s [ i ] s[i] s[i],分别表示到时刻i时的人的数量与乘客到达时间的前缀和.

那么我们考虑设 f [ i ] f[i] f[i]表示时刻i发一辆车时的最少总等待时间,然后枚举上一次的发车时刻 j j j来转移,时间复杂度 O ( m a x { t [ i ] } 2 ) O(max \left \{ t[i] \right \}^2) O(max{t[i]}2).

对于这个算法,我们可以列出方程:
f [ i ] = m i n j = 0 i − m ( f [ j ] + i ∗ ( c [ i ] − c [ j ] ) − s [ i ] + s [ j ] ) f[i]=min_{j=0}^{i-m}(f[j]+i*(c[i]-c[j])-s[i]+s[j]) f[i]=minj=0im(f[j]+i(c[i]c[j])s[i]+s[j])

考虑一个状态 f [ i ] f[i] f[i] f [ j ] f[j] f[j]转移过来,可以发现若 i − j > 2 m i-j>2m ij>2m,明显可以让这两次发车之间多发一次车来使答案不会变得更劣,那么我们就可以将枚举j变成枚举时差来优化上面的算法,时间复杂度 O ( m max ⁡ { t [ i ] } ) O(m\max \left \{ t[i] \right \}) O(mmax{t[i]}).

那么方程就变为:

f [ i ] = min ⁡ j = m 2 m { f [ i − j ] + i ∗ ( c [ i ] − c [ i − j ] ) − s [ i ] + s [ i − j ] } f[i]=\min_{j=m}^{2m}\{f[i-j]+i*(c[i]-c[i-j])-s[i]+s[i-j]\} f[i]=j=mmin2m{f[ij]+i(c[i]c[ij])s[i]+s[ij]}

继续考虑优化这个算法,发现一辆车的发车时间只会是 t [ i ] + j t[i]+j t[i]+j,其中 0 ≤ j ≤ m 0\leq j\leq m 0jm.那么我们可以只计算有用的状态,不去计算无用的状态,也就是说我们枚举到一个状态 f [ i ] f[i] f[i]时,判断这个状态是否有一个 t [ j ] t[j] t[j],使得 t [ j ] ≤ i ≤ m t[j]\leq i\leq m t[j]im,具体可以通过 c [ i ] − c [ i − m ] c[i]-c[i-m] c[i]c[im]是否为 0 0 0来判断.这样我们就可以做到 O ( n m 2 + max ⁡ { t [ i ] } ) O(nm^2+\max \left \{ t[i] \right \}) O(nm2+max{t[i]}).

然后貌似还可以把这个算法优化一下,类似于斜率优化,可以做到 O ( n m + n log ⁡ n ) O(nm+n\log n) O(nm+nlogn)但是我不会.

O ( n m 2 + m a x { t [ i ] } ) O(nm^2+max \left \{ t[i] \right \}) O(nm2+max{t[i]})算法代码如下:

#include<bits/stdc++.h>
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=500,M=100,T=5*1000000;
const int INF=(1<<29)-1;
 
int n,m,t[N+9];
int c[T+9],s[T+9],mt;
//int f[N+9][M*2+9];
int f[T+9];
 
Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i){
    scanf("%d",&t[i]);
    mt=max(mt,t[i]);
    ++c[t[i]];s[t[i]]+=t[i];
  }
}
 
Abigail work(){
  mt+=m-1;
  sort(t+1,t+1+n);
  for (int i=1;i<=mt;++i)
    c[i]+=c[i-1],s[i]+=s[i-1];
  for (int i=0;i<=mt;++i)
    f[i]=i*c[i]-s[i];
  for (int i=0;i<=mt;++i){
    if (!(c[i]-c[i-m])&&i>m){
      f[i]=f[i-m];      //避免一些麻烦的处理 
      continue;
    }
    f[i]=i*c[i]-s[i];
    for (int j=max(0,i-(m<<1));j<=i-m;++j)
      f[i]=min(f[i],f[j]+i*(c[i]-c[j])-s[i]+s[j]);
  }
}
 
Abigail outo(){
  int ans=INF;
  for (int i=t[n];i<=mt;++i)
    ans=min(ans,f[i]);
  printf("%d\n",ans);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值