洛谷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\)。
则,状态转移方程为:
示例程序:
#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\)。
则,状态转移方程为:
这样,问题就变成了一个 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;
}
其它
在费用提前计算思想的基础上,本题还可以用斜率优化。
感兴趣的同学可以参考《信息学奥赛一本通 提高篇》或者上网进一步了解。