CF311B Cats Transport

原题面:CFLuogu

斜率优化,kixi 均单调,单调队列。

neko nyanyanya~

简述

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

分析

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

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

对于抽象后的问题,设 si=jitj,设 fi,j 表示将前 i 个数分为 j 段的最小代价和,转移时先枚举段数 k,再枚举最后一段,则有:

fi,k=minj=0i1{fj,k1+l=j+1i(titl)}=minj=0i1{fj,k1+(ij)ti(sisj)}=itisi+minj=0i1{(fj,k1+sj)tij}

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

xi=iyi=fi,k1+sjki=tibi=fi,kiti+si

显然应最小化 bi,又 xiki 都随 i 的增加单调不降,单调队列维护下凸包即可。
总复杂度 O(n+mlogm+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 @   Luckyblock  阅读(54)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示