38 任务安排 题解

任务安排1

题面

\(n\) 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。

从零时刻开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间为 \(t_i\)

在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。

每个任务的费用是它的完成时刻乘以一个费用系数 \(f_i\)

请确定一个分组方案,使得总费用最小。

\(1\le n \le 5000\)\(0 \le s \le 50\)\(1\le t_i,f_i \le 100\)

题解

这道题一个比较容易想到的dp状态是设 \(f(i,j)\) 表示前 \(i\) 个任务,分成 \(j\) 组的最小费用

转移 \(f(i,j) = min \{ f(k, j - 1) + (s \times j + sumT[i]) \times (sumf[i] - sumf[k]) \}\)

这个转移的时间复杂度为 \(O(n^3)\)

事实上,这道题并没有规定要把任务分成多少组,我们刚才记录组数是为了统计机器启动了多少次(每次启动都会花费 \(s\) )的启动时间

我们在执行一组任务的时候,并不容易知道之前启动了多少次,但是执行这组任务会使这组以及后面的任务都累加上这次启动所花费的时间,所以我们可以提前将这次启动的花费计算出来

\(f(i)\) 表示把前 \(i\) 个任务分成若干组执行的最小费用

\[f(i) = \min_{0 \le k < i} \{ f(k) + sumT[i] \times (sumf[i] - sumf[k]) + s \times (sumf[n] - sumf[k]) \} \]

也就是说我们并没有直接求出每批任务的完成时刻,而是在一批任务开始对后续任务产生影响时就提前把费用累计到答案里,这就是大名鼎鼎的费用提前计算,这样就可以在 \(O(n^2)\) 的时间复杂度内解决此题

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
const int N = 5e3 + 10;

int n, s;

ll f[N], sumf[N], sumt[N];

int main () {
    cin >> n >> s;
    for (int i = 1; i <= n; i ++) {
        cin >> sumt[i] >> sumf[i];
        sumt[i] += sumt[i - 1];
        sumf[i] += sumf[i - 1];
    }

    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] * (sumf[i] - sumf[j]) + s * (sumf[n] - sumf[j]));
        }
    }

    cout << f[n] << endl;


    return 0;
}

任务安排2

题面

同上,数据范围发生改变

\(n\) 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。

从零时刻开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间为 \(t_i\)

在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。

每个任务的费用是它的完成时刻乘以一个费用系数 \(f_i\)

请确定一个分组方案,使得总费用最小。

\(1 \le n \le 3 \times 10^5\)\(1 \le s \le 2^8\)\(1\le T_i \le 2^8\)\(0 \le C_i \le 2^8\)

题解

这道题和上一题唯一不同的地方是数据范围,上一题可以接受 \(O(N^2)\) 的时间复杂度

但这题又要想办法优化到更优

先将转移方程写出来

\[f(i) = \min_{0 \le k < i} \{ f(k) + sumT[i] \times (sumf[i] - sumf[k]) + s \times (sumf[n] - sumf[k]) \} \]

发现这个方程的难点在于 \(\min\) 里面有和 \(i,j\) 同时有关的项,所以不能用单调队列优化

但是这种可以想想能不能用斜率优化去搞一搞,因为斜率优化就是用来解决这种某项和 \(i,j\) 同时有关的问题的

先将原式变形,将题目中的 \(s\) 写成 \(C\) ,方便区分

\[f_j = (C + st_i)sf_j - st_isf_i - Cf_n + f_i \]

发现好像可以化成 \(y = ax + b\) 的形式

因为 \(st_i\) 是递增的,所以每次排除队头斜率小于 \(C + st_i\) 的决策,每次转移直接取队头

每次转移的时间复杂度为 \(O(1)\) ,总时间复杂度为 \(O(N)\)

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
const int N = 3e5 + 10;

int n, C;
int q[N];
ll f[N], sumf[N], sumt[N];


double slope (int i, int j) {
    return (double)(f[j] - f[i]) / (sumf[j] == sumf[i] ? 1e-9 : sumf[j] - sumf[i]);
}

int main () {
    cin >> n >> C;
    for (int i = 1; i <= n; i ++) {
        cin >> sumt[i] >> sumf[i];
        sumt[i] += sumt[i - 1];
        sumf[i] += sumf[i - 1];
    }

    memset (f, 0x3f, sizeof f);
    f[0] = 0;
    int h = 1, t = 0;
    for (int i = 1; i <= n; i ++) {
        while (h < t && slope (q[t - 1], q[t]) >= slope (q[t], i - 1)) t --;
        q[ ++ t] = i - 1;
        while (h < t && slope (q[h], q[h + 1]) <= C + sumt[i]) h ++;
        int j = q[h];
        f[i] = f[j] + sumt[i] * (sumf[i] - sumf[j]) + C * (sumf[n] - sumf[j]);
    }

    cout << f[n] << endl;


    return 0;
}

任务安排3

题面

同上,\(T\) 可能为负数

\(n\) 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。

从零时刻开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间为 \(t_i\)

在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。

每个任务的费用是它的完成时刻乘以一个费用系数 \(f_i\)

请确定一个分组方案,使得总费用最小。

\(1 \le n \le 3 \times 10^5\)\(1 \le s \le 2^8\)\(|T_i| \le 2^8\)\(0 \le C_i \le 2^8\)

题解

\[f_j = (C + st_i)sf_j - st_isf_i - Cf_n + f_i \]

这道题和上个题的区别是 \(T_i\) 可能为负数,所以 \(sumT[i]\) 就不满足递增的性质了,所以我们要在刚才的那道题的代码上做一些修改

因为 \(st_i\) 不满足递增,所以我们不能每次将队头的决策弹出,而是要保留整个凸包,然后每次二分查找即可

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
const int N = 3e5 + 10;

int n, C;
int q[N];
ll f[N], sumf[N], sumt[N];


double slope (int i, int j) {
    return (double)(f[j] - f[i]) / (sumf[j] == sumf[i] ? 1e-9 : sumf[j] - sumf[i]);
}

//二分查找
int binary_search (int l, int r, ll x) {
    if (l == r) return q[l];
    while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (slope (q[mid - 1], q[mid]) <= x) l = mid;
        else r = mid - 1;
    }
    return q[l];
}

int main () {
    cin >> n >> C;
    for (int i = 1; i <= n; i ++) {
        cin >> sumt[i] >> sumf[i];
        sumt[i] += sumt[i - 1];
        sumf[i] += sumf[i - 1];
    }
    memset (f, 0x3f, sizeof f);
    f[0] = 0;
    int h = 1, t = 0;
    for (int i = 1; i <= n; i ++) {
        while (h < t && slope (q[t - 1], q[t]) >= slope (q[t], i - 1)) t --;
        q[ ++ t] = i - 1;
        int j = binary_search (h, t, C + sumt[i]);
        f[i] = f[j] + sumt[i] * (sumf[i] - sumf[j]) + C * (sumf[n] - sumf[j]);
    }

    cout << f[n] << endl;


    return 0;
}
posted @ 2025-10-09 21:19  michaele  阅读(5)  评论(0)    收藏  举报