P2365 任务安排

知识点:斜率优化

原题面:Luogu

三 步 走 战 略

简述

给定一列 n 个有序的物品,每个物品有两个属性 (ti,gi),给定参数 s
要求将物品分为任意段,第 i[li,ri] 的代价为 (is+j=liritj)k=liri,要求最小化分段的代价之和。
1n50000s501ti,gi100
1S,128MB。

分析

以下将会给出三种做法。

O(n3)

发现分到第几段对答案有影响,设 fi,j 表示将前 i 个任务分为 j 段的最小费用和,转移时枚举段数 k 和最后一段,则有:

fi,k=minj=0i1{fj,k1+(ks+k=j+1itk)l=j+1igl}

预处理前缀和,暴力转移时间复杂度 O(n3),空间复杂度 O(n2)。空间和时间都菜爆了。

O(n2)

发现在上述算法中必须枚举分到第几段,考虑能否优化掉状态的这一维,并优化转移。
这里用到了一种叫做「费用提前计算」的思想。发现每次转移将 [j+1,i] 这段分出后,后续元素的代价里都会加上 kg,考虑在状态转移中加上这部分的影响。具体地,将状态删去一维,方程改写为如下所示:

fi=minj=0i1{fj+k=1itkl=j+1igl+s(k=j+1ngk)}

状态转移方程很容易理解。此时已经无法准确定义 f 的含义了,但 fn 一定表示将所有物品划分为某几段的最小代价和,且这样转移一定可以保证 fn 的正确性。

预处理前缀和后暴力转移即可,时间复杂度 O(n2),空间复杂度 O(n)

O(n)

上述 O(n2) 算法已经可以通过原题了,但这还不够。

stx=i=1xtisgx=i=1xgi,代入转移方程并略作变换:

fi=minj=0i1{fj+sti(sgisgj)+s(sgnsgj)}fistisgissgn=minj=0i1{(fjssgj)stisgj}

这是一个显然的斜率优化的形式,设:

xi=sgiyi=fissgiki=stibi=fistisgissgn

由于 0ti,gikixi 均单调递增,套路地单调队列维护下凸包即可。
总时空复杂度均为 O(n)

代码

O(n)

复制复制
//知识点:斜率优化,费用提前计算
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
#define LD long double
const int kN = 5e3 + 10;
const LL kInf = 9e18 + 2077;
//=============================================================
int n, h = 1, t, q[kN];
LL s, ans = kInf, f[kN], sumt[kN], sumg[kN];
//=============================================================
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 sumg[x_];
}
LD Y(int x_) {
return f[x_] - s * sumg[x_];
}
LD K(int x_, int y_) {
if (X(x_) == X(y_)) return (Y(y_) > Y(x_) ? 1e18 : -1e18);
return (LD) ((Y(y_) - Y(x_)) / (X(y_) - X(x_)));
}
int Query(int now_) {
LD know = sumt[now_];
while (h < t && K(q[h], q[h + 1]) <= know) ++ h;
return q[h];
}
void Insert(int now_) {
while (h < t && K(q[t - 1], q[t]) >= K(q[t - 1], now_)) -- t;
q[++ t] = now_;
}
//=============================================================
int main() {
n = read(), s = read();
for (int i = 1; i <= n; ++ i) {
sumt[i] = sumt[i - 1] + read();
sumg[i] = sumg[i - 1] + read();
}
Insert(0);
for (int i = 1; i <= n; ++ i) {
int j = Query(i);
f[i] = f[j] + sumt[i] * (sumg[i] - sumg[j]) +
s * (sumg[n] - sumg[j]);
Insert(i);
}
printf("%lld\n", f[n]);
return 0;
}

O(n2)

//知识点:DP,费用提前计算
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 5e3 + 10;
const LL kInf = 9e18 + 2077;
//=============================================================
int n, s;
LL ans = kInf, f[kN], sumt[kN], sumg[kN];
//=============================================================
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;
}
LL Calc(int l_, int r_) {
LL t = sumt[r_];
LL g = sumg[r_] - sumg[l_ - 1];
return t * g + 1ll * s * (sumg[n] - sumg[l_ - 1]);
}
//=============================================================
int main() {
n = read(), s = read();
for (int i = 1; i <= n; ++ i) {
sumt[i] = sumt[i - 1] + read();
sumg[i] = sumg[i - 1] + read();
}
memset(f, 63, sizeof (f));
f[0] = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j < i; ++ j) {
Chkmin(f[i], f[j] + Calc(j + 1, i));
}
}
printf("%lld\n", f[n]);
return 0;
}

O(n3)

为了不 MLE 开了 int = =
会炸的(

//知识点:DP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
#define LD long double
const int kN = 5e3 + 10;
const int kInf = 1e9 + 2077;
//=============================================================
int n, s;
int ans = kInf, f[kN][kN], sumt[kN], sumg[kN];
//=============================================================
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(int &fir, int sec) {
if (sec < fir) fir = sec;
}
int Calc(int k_, int l_, int r_) {
int t = k_ * s + sumt[r_];
int g = sumg[r_] - sumg[l_ - 1];
return t * g;
}
//=============================================================
int main() {
n = read(), s = read();
for (int i = 1; i <= n; ++ i) {
sumt[i] = sumt[i - 1] + read();
sumg[i] = sumg[i - 1] + read();
}
memset(f, 63, sizeof (f));
f[0][0] = 0;
for (int k = 1; k <= n; ++ k) {
for (int i = k; i <= n; ++ i) {
for (int j = 0; j < i; ++ j) {
Chkmin(f[i][k], f[j][k - 1] + Calc(k, j + 1, i));
}
}
}
for (int i = 1; i <= n; ++ i) Chkmin(ans, f[n][i]);
printf("%d\n", ans);
return 0;
}
posted @   Luckyblock  阅读(96)  评论(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】
点击右上角即可分享
微信分享提示