首页 > 技术文章 > P5017 摆渡车

luckyblock 2020-06-13 17:30 原文


知识点:线性DP,DP优化

我懂个卵子的DP。

原题面

算法一

\(f_i\)\(i\) 时间发车,发车时等待的时间和的最小值。则显然有:

\[f_i = \min_{j\le i-m}\left\{f_j + \sum_{j < t_k \le i}^{n}{(i-t_k)}\right\} \]

对于每个 \(f_i\),当在 \(i\) 时刻时发第一班车,\(f_i\)最大,则其初始值为:

\[f_i = \sum_{t_k<i}^{n} {(i-t_k)} \]

为保证载上所有人,最后一班车需在 \([t_{max}, t_{max}+m)\)内发车,则:

\[ans = \min_{i=t_{max}}^{t_{max}+m}(f_i) \]

复杂度 \(O(nt^2)\),期望得分 \(30\text{pts}\)

算法二

前缀和优化。

设 :

\[cnt_i=\sum\limits_{t_k\le i} 1,\ pos_i = \sum\limits_{t_k\le i}{t_k} \]

对于上式中的 \(\sum\limits_{j < t_k \le i}{i-t_k}\),有:

\[\sum\limits_{j < t_k \le i}{i-t_k} = (cnt_i-cnt_j)\times i - (pos_i-pos_j)\]

替换状态转移方程。
复杂度 \(O(t^2)\),期望得分 \(50\text{pts}\)

算法三

优化转移方程。

对于状态转移方程:

\[f_i = \min_{j\le i-m}(f_j + (cnt_i-cnt_j)\times i - (pos_i-pos_j)) \]

显然,若 \(j\le i-2m\),则在 \(j+m\) 时刻可多发一班车,不影响在 \(i\) 时刻发车,且答案不会变劣。
即:停车时间 \(i- (j+m) < m\)

故转移方程可替换为:

\[f_i = \min_{i-2m\le j\le i-m}(f_j + (cnt_i-cnt_j)\times i - (pos_i-pos_j)) \]

复杂度 \(O(mt)\),期望得分 \(70\text{pts}\)

算法四

减去无用状态。

\(cnt_i=cnt_{i-m}\),说明时间段 \([i-m,i]\)内没有需要坐车的人。
则在 \(i-m\) 时刻发车,在 \(i\) 时刻不发车,不会使答案变劣,\(f_i\) 是一个无用状态。

则可将状态转移方程改为:

\[f_i = \begin{cases}f_{i-m} &cnt_i=cnt_{i-m}\\ \min_{i-2m\le j\le i-m}(f_j + (cnt_i-cnt_j)\times i - (pos_i-pos_j))& \text{otherwise}\end{cases} \]

当满足 \(t_i = t_{i-1}+m\) 时,有用的位置最多,为 \(nm\) 个。

复杂度 \(O(nm^2 + t)\),期望得分 \(100\text{pts}\)

算法五

由算法三,停车时间 \(i- (j+m) < m\)
车往返一次时间 \(m\),则人等车时间 \(< 2m\)

\(f_{i,j}\) 表示第 \(i\) 个人,等待时间为 \(j\),且前 \(i\) 个人已到达的时间最小值。

初始值 \(f_{1,i} = i\)

分类讨论:

  1. \(t_{i+1} \le t_{i+1} + j\),则第 \(i+1\) 个人可和 第 \(i\) 个人坐同一辆车走。
    \(i+1\) 个人的等待时间 \(k = t_i+j-t_{i+1}\)
    状态转移方程式为:

\[f_{i+1, k} = \min(f_{i+1, k},f_{i,j}+k ) \]

  1. 否则,枚举第 \(i+1\) 个人的等待时间 \(k\)

    \[k\in [\max(0, t_i + j + m - t_{i+1}), 2m) \]

    状态转移方程式同上,为:

    \[f_{i+1,k} = \min (f_{i+1}, k, f_{i,j} + k) \]

复杂度 \(O(nm^2)\),期望得分 \(100\text{pts}\)


算法四

//知识点:DP
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 4e6 + 2077;
//=============================================================
ll n, m, ans, maxt;
ll cnt[kMaxn], pos[kMaxn], f[kMaxn];
//=============================================================
inline ll read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) {
    ll t = read(); 
    maxt = std :: max(maxt, t);
    cnt[t] ++, pos[t] += t;
  }
  for (int i = 1; i < maxt + m; ++ i) {
    cnt[i] += cnt[i - 1];
    pos[i] += pos[i - 1];
  }
  memset(f, 63, sizeof(f)); ans = f[0];
  for (int i = 0; i < maxt + m; ++ i) {
    if (i >= m && cnt[i - m] == cnt[i]) {
      f[i] = f[i - m];
      continue;
    }
    f[i] = cnt[i] * i - pos[i];
    for (int j = std :: max(i - 2 * m, 0ll); j <= i - m; ++ j) {
      f[i] = std :: min(f[i], 
      f[j] + (cnt[i] - cnt[j]) * i - (pos[i] - pos[j]));
    }
  }
  
  for (int i = maxt; i < maxt + m; ++ i) {
    ans = std :: min(ans, f[i]);
  }
  printf("%lld\n", ans);
  return 0;
}

算法五

空间需求比较奇怪,总是RE,于是开了很大。

//
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 5010;
//=============================================================
ll n, m, ans, t[kMaxn], f[kMaxn][kMaxn / 4];
//=============================================================
inline ll read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) t[i] = read(); 
  std :: sort(t + 1, t + n + 1);
  
  memset(f, 63, sizeof(f)); ans = f[0][0];
  for (int i = 0; i <= 2 * m; ++ i) f[1][i] = i;
  
  for (int i = 1; i <= n; ++ i) {
    for (int j = 0; j < 2 * m; ++ j) {
      if (t[i + 1] <= t[i] + j) {
        int k = t[i] + j - t[i + 1];
        f[i + 1][k] = std :: min(f[i + 1][k], f[i][j] + k);
      } else {
        for (int k = std :: max(0ll, t[i] + j + m - t[i + 1]); k < 2 * m; ++ k) {
          f[i + 1][k] = std :: min(f[i + 1][k], f[i][j] + k);
        }
      }
    }
  }
  for (int i = 0; i <= m; ++ i) ans = std :: min(ans, f[n][i]);
  printf("%lld\n", ans);
  return 0;
}

推荐阅读