洛谷P2365 任务安排 题解 线性DP+费用提前计算思想

题目链接:https://www.luogu.com.cn/problem/P2365

题目大意:

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

从零时刻开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间为 \(t_i\)。在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。

每个任务的费用是它的完成时刻乘以一个费用系数 \(f_i\)。请确定一个分组方案,使得总费用最小。

解题思路

朴素做法

首先,因为这里会用到一段 \(t_i\) 之和和一段 \(f_i\) 之和,所以定义两个前缀和:

  • \(T_i = \sum\limits_{j=1}^i t_j\)
  • \(F_i = \sum\limits_{j=1}^i f_j\)

同时定义状态 \(g_{i,j}\) 表示前 \(i\) 个任务分成 \(j\) 批(并且第 \(i\) 个任务是第 \(j\) 批的最后一个任务)的情况下,完成前 \(i\) 个任务的最小花费。特殊地,\(g_{0,0} = 0\)

则,状态转移方程为:

\[g_{i,j} = \min\limits_{0 \le k \lt i}\{\ g_{k, j-1} + (F_i - F_k) \cdot (T_i + s \cdot j) \ \} \]

示例程序:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5005;

int n;
long long s, t[maxn], T[maxn], f[maxn], F[maxn], g[maxn][maxn];

int main() {
    cin >> n >> s;
    for (int i = 1; i <= n; i++) {
        cin >> t[i] >> f[i];
        T[i] = T[i-1] + t[i];
        F[i] = F[i-1] + f[i];
    }
    memset(g, 0x3f, sizeof g);
    g[0][0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            for (int k = 0; k < i; k++) {
                g[i][j] = min(g[i][j], g[k][j-1] + (F[i] - F[k]) * (T[i] + s * j));
            }
        }
    }
    long long ans = 1e18;
    for (int i = 1; i <= n; i++)
        ans = min(ans, g[n][i]);
    cout << ans << endl;
    return 0;
}

但是,这样设计状态是 2D/1D 的(状态 \(O(n^2)\),状态转移 \(O(n)\)),整体时间复杂度是 \(O(n^3)\),而 \(n \le 5000\),会超时。

费用提前计算

考虑前 \(i\) 个任务如果划分为了第一段,则后面的 \(n-i\) 个任务的完成时间都会额外增加 \(s\),对于后面的任务来说,由于前 \(i\) 个任务划分成了第一段而导致它们额外增加的代价是 \(s \cdot (F_n - F_i)\)\(\Rightarrow\) 我们可以把这个额外增加的代价算到第一段里面。

如果把这部分额外增加的代价算到当前这段里面里面,那么对于后面的任务 \([i+1, n]\),它们就不会受前面的任务段数影响了。

于是,我们可以定义一个一维的状态 \(g_i\),表示将前 \(i\) 个任务分成若干段的情况下,前 \(i\) 个任务的代价加上对未来的任务造成的额外代价之和的最小值。特殊地,\(g_0 = 0\)

则,状态转移方程为:

\[g_i = \min\limits_{0 \le j \lt i}\{\ g_j + (s + T_i) \cdot (F_i - F_j) + s \cdot (F_n - F_i) \ \} \]

这样,问题就变成了一个 1D/1D 类的问题(状态 \(O(n)\),状态转移 \(O(n)\)),总的时间复杂度 \(O(n^2)\),能通过此题。

示例程序:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5005;

int n;
long long s, t[maxn], T[maxn], f[maxn], F[maxn], g[maxn];

int main() {
    cin >> n >> s;
    for (int i = 1; i <= n; i++) {
        cin >> t[i] >> f[i];
        T[i] = T[i-1] + t[i];
        F[i] = F[i-1] + f[i];
    }
    for (int i = 1; i <= n; i++) {
        g[i] = 1e18;
        for (int j = 0; j < i; j++) {
            g[i] = min(g[i], g[j] + (s + T[i]) * (F[i] - F[j]) + s * (F[n] - F[i]));
        }
    }
    cout << g[n] << endl;
    return 0;
}

其它

在费用提前计算思想的基础上,本题还可以用斜率优化。

感兴趣的同学可以参考《信息学奥赛一本通 提高篇》或者上网进一步了解。

参考链接:https://www.cnblogs.com/LittleTwoawa/p/17220583.html

posted @ 2025-04-28 15:42  quanjun  阅读(27)  评论(0)    收藏  举报