CF311B Cats Transport

原题面:CFLuogu

斜率优化,\(k_i\)\(x_i\) 均单调,单调队列。

neko nyanyanya~

简述

一条数轴上有 \(n\) 个点,第 \(i\) 个点与第 \(i-1\) 个点的距离为 \(d_i\),有 \(p\) 个人在第 \(1\) 个点上。有 \(m\) 只 neko,第 \(i\) 只 neko 位于第 \(h_i\) 个点上,neko 会在 \(T_i\) 时间开始等待人的到来。
没个人可以从任意时间从第 \(1\) 个点出发,按编号顺序依次经过各点,速度为 1 个单位长度 1 秒,中间不停下。每经过一个点,就会将该点上处于等待状态的 neko 接上。
每只 neko 的等待时间为其被接上时间与开始等待时间的差。要求安排每个人出发的时间,最小化 neko 等待的时间之和。
\(2\le h_i\le n\le 10^5\)\(1\le m\le 10^5\)\(1\le p\le 100\)\(1\le d_i\le 10^4\)\(1\le T_i\le 10^9\)
2S,256MB。

分析

可以简单地 YY 出一个与 \(t\) 相关的 DP 状态,但 \(t\) 过大,猜测 DP 状态与 \(t\) 无关,考虑找下结论。可以发现每个人接上的 neko 中,至少有一只恰好刚开始等待人的到来。正确性显然,若所有 neko 都是等待了一段时间后再被接上,不如提前人的出发时间,直到有一只刚开始等待,在不影响接上了哪些 neko 的前提下减少等待时间之和。

前缀和预处理点 \(1\) 到各点的距离 \(\operatorname{dis}_i\)。对于每只 neko,容易求得一个人从何时出发能够恰好接上它,显然该时间为 \(t_i = T_i - \operatorname{dis}_{h_i}\)
考虑将所有 neko 按照 \(t_i\) 排序。显然在满足一开始的结论的前提下,每个人接到的 neko 都对应排序后的一段区间 \([l,r]\),刚开始等的 neko 一定是第 \(r\) 只 neko(即 \(t_i\) 最大的),则这个人的出发时间为 \(t_r\),区间内 neko 的等待时间之和为 \(\sum t_r - t_i\)
问题可以抽象为给定一长度为 \(m\) 的数列 \(t\),要求将 \(t\) 分为 \(p\) 段,每段的代价为该段最右的数减去该段每个值的和,最小化代价和。

对于抽象后的问题,设 \(s_i = \sum_{j\le i}t_j\),设 \(f_{i,j}\) 表示将前 \(i\) 个数分为 \(j\) 段的最小代价和,转移时先枚举段数 \(k\),再枚举最后一段,则有:

\[\begin{aligned} f_{i,k} &= \min_{j = 0}^{i - 1} \left\{ f_{j, k - 1} + \sum_{l = j + 1}^{i} \left( t_i - t_l \right) \right\}\\ &= \min_{j = 0}^{i - 1} \left\{ f_{j, k - 1} + (i - j)t_i - (s_i - s_j) \right\}\\ &= i\cdot t_i -s_i + \min_{j = 0}^{i - 1} \left\{ \left(f_{j, k - 1} +s_j\right) - t_i\cdot j \right\} \end{aligned}\]

方程中出现了乘积项,先通过枚举固定 \(k\) 后,这显然是一个可以斜率优化的形式,套路地设:

\[\begin{aligned} x_i &= i\\ y_i &= f_{i,k - 1} + s_j\\ k_i &= t_i\\ b_i &= f_{i,k} - i\cdot t_i + s_i \end{aligned}\]

显然应最小化 \(b_i\),又 \(x_i\)\(k_i\) 都随 \(i\) 的增加单调不降,单调队列维护下凸包即可。
总复杂度 \(O(n + m\log m + pm)\) 级别。

代码

//知识点:斜率优化
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
#define LD long double
const int kN = 1e5 + 10;
//=============================================================
int n, m, p, head = 1, tail, q[kN];
LL sumd[kN], t[kN], s[kN], f[kN][101];
//=============================================================
inline int 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;
}
void Chkmin(LL &fir, LL sec) {
  if (sec < fir) fir = sec;
}
LD X(int x_) {
  return x_;
}
LD Y(int id_, int x_) {
  return f[x_][id_] + s[x_];
}
LD K(int id_, int x_, int y_) {
  if (X(x_) == X(y_)) return (Y(id_, y_) > Y(id_, x_) ? 1e18 : -1e18);
  return (LD) ((Y(id_, y_) - Y(id_, x_)) / (X(y_) - X(x_)));
}
int Query(int id_, int now_) {
  LD know = t[now_];
  while (head < tail && K(id_, q[head], q[head + 1]) <= know) ++ head;
  return q[head];
}
void Insert(int id_, int now_) {
  while (head < tail && 
         K(id_, q[tail - 1], q[tail]) >= K(id_, q[tail - 1], now_)) -- tail;
  q[++ tail] = now_;
}
void Init() {
  n = read(), m = read(), p = read();
  for (int i = 2; i <= n; ++ i) sumd[i] = sumd[i - 1] + read();
  for (int i = 1; i <= m; ++ i) {
    int h = read(), nowt = read();
    t[i] = nowt - sumd[h];
  }
  std::sort(t + 1, t + m + 1);
  for (int i = 1; i <= m; ++ i) s[i] = s[i - 1] + t[i];
}
//=============================================================
int main() { 
  Init();
  memset(f, 63, sizeof(f));
  f[0][0] = 0;
  for (int k = 1; k <= p; ++ k) {
    head = 1, tail = 0;
    Insert(k - 1, 0);
    for (int i = 1; i <= m; ++ i) {
      if (i >= k) {
        int j = Query(k - 1, i);
        f[i][k] = f[j][k - 1] + 1ll * (i - j) * t[i] - s[i] + s[j];
      }
      Insert(k - 1, i);
    }
  }
  printf("%lld\n", f[m][p]);
  return 0; 
}
posted @ 2021-02-01 06:22  Luckyblock  阅读(52)  评论(0编辑  收藏  举报