ChungZH 的洛谷博客

菜 /kk

P2365 任务安排

欢迎访问我的博客:blog.chungzh.cn

摘自《进阶指南》。

解法一

求出 $t, f$ 的前缀和 $sumt, sumf$。设 $F(i, j)$ 表示把前 $i$ 个任务分成 $j$ 批执行的最小费用。状态转移方程为:

$$ F(i, j) = min_{0<=k<i}\{F(k, j-1)+(S\times j+sumt[i])\times(sumf[i]-sumf[k])\} $$

时间复杂度为 $O(N^3)$

解法二

上一个解法中需要批数 $j$ 是因为我们需要知道机器启动了多少次(每次启动都需要 $S$ 单位时间),才能知道当前这批任务的完成时刻。

事实上,机器因为执行这批任务而花费的启动时间 $S$,会累加到在此之后所有任务的完成时间。设 $F(i)$ 表示把前 $i$ 个任务分成若干批执行的最小费用,状态转移方程为:

$$ F(i)=min_{0<=j<i}\{F(j)+sumt[i]\times (sumf[i]-sumf[j])+S\times (sumf[N]-sumf[j])\} $$

在上式中,$sumt[i]$ 是忽略机器启动时间时,这批任务的完成时刻。因为这批任务的执行,机器的启动时间 $S$ 会对第 $j+1$ 个之后的所有任务产生影响,就把这部分补充到费用中。

通过这样,我们就不需要知道之前启动了多少次也可以计算出费用了。

这种思想叫做 费用提前计算

时间复杂度为 $O(N^2)$

#include <algorithm>
#include <cmath>
#include <deque>
#include <iostream>
using namespace std;
const int MAXN = 5005;
int n, s;
long long f[MAXN], sumt[MAXN], sumc[MAXN];
int main() {
  ios::sync_with_stdio(false);
  cin >> n >> s;
  for (int i = 1; i <= n; i++) {
    int t, c;
    cin >> t >> c;
    sumt[i] = sumt[i - 1] + t;
    sumc[i] = sumc[i - 1] + c;
  }
  memset(f, 0x3f, sizeof f);
  f[0] = 0;
  for (int i = 1; i <= n; i++) {
    for (int j = 0; j < i; j++) {
      f[i] = min(f[i], f[j] + sumt[i] * (sumc[i] - sumc[j]) + s * (sumc[n] - sumc[j]));
    }
  }
  cout << f[n] << endl;
  return 0;
}

解法三

斜率优化。


2022-08-20 14:54:56 in 题解