P2365 任务安排

知识点:斜率优化

原题面:Luogu

三 步 走 战 略

简述

给定一列 \(n\) 个有序的物品,每个物品有两个属性 \((t_i, g_i)\),给定参数 \(s\)
要求将物品分为任意段,第 \(i\)\([l_i,r_i]\) 的代价为 \(\left(is + \sum_{j=l_i}^{r_i} t_j\right)\cdot \sum_{k=l_i}^{r_i}\),要求最小化分段的代价之和。
\(1\le n\le 5000\)\(0\le s\le 50\)\(1\le t_i,g_i\le 100\)
1S,128MB。

分析

以下将会给出三种做法。

\(O(n^3)\)

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

\[f_{i,k} = \min_{j=0}^{i - 1} \left\{ f_{j,k-1} + \left(ks + \sum_{k=j+1}^{i} t_k \right)\sum_{l=j + 1}^{i} g_l \right\} \]

预处理前缀和,暴力转移时间复杂度 \(O(n^3)\),空间复杂度 \(O(n^2)\)。空间和时间都菜爆了。

\(O(n^2)\)

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

\[f_i = \min_{j=0}^{i-1}\left\{ f_j + \sum_{k=1}^{i} t_k\sum_{l=j + 1}^{i} g_l + s\left( \sum_{k = j + 1}^{n} g_k\right)\right\} \]

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

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

\(O(n)\)

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

\(st_x = \sum_{i=1}^{x} t_i\)\(sg_x = \sum_{i=1}^x g_i\),代入转移方程并略作变换:

\[\begin{aligned} f_i &= \min_{j=0}^{i-1}\left\{ f_j + st_i\left( sg_i - sg_j \right) + s\left( sg_n - sg_j\right)\right\}\\ f_i - st_i\cdot sg_i - s\cdot sg_n&= \min_{j=0}^{i-1} \left\{ \left(f_j - s\cdot sg_j \right) - st_i\cdot sg_j \right\} \end{aligned}\]

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

\[\begin{aligned} x_i &= sg_i\\ y_i &= f_ i- s\cdot sg_i\\ k_i &= st_i\\ b_i &= f_i - st_i\cdot sg_i - s\cdot sg_n \end{aligned}\]

由于 \(0\le t_i,g_i\)\(k_i\)\(x_i\) 均单调递增,套路地单调队列维护下凸包即可。
总时空复杂度均为 \(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(n^2)\)

 //知识点: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(n^3)\)

为了不 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 @ 2021-02-01 21:52  Luckyblock  阅读(92)  评论(0编辑  收藏  举报