CF311B Cats Transport
斜率优化,\(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\),再枚举最后一段,则有:
方程中出现了乘积项,先通过枚举固定 \(k\) 后,这显然是一个可以斜率优化的形式,套路地设:
显然应最小化 \(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;
}