题解:洛谷 P1113 杂务
【题目来源】
【题目描述】
John 的农场在给奶牛挤奶前有很多杂务要完成,每一项杂务都需要一定的时间来完成它。比如:他们要将奶牛集合起来,将他们赶进牛棚,为奶牛清洗乳房以及一些其它工作。尽早将所有杂务完成是必要的,因为这样才有更多时间挤出更多的牛奶。
当然,有些杂务必须在另一些杂务完成的情况下才能进行。比如:只有将奶牛赶进牛棚才能开始为它清洗乳房,还有在未给奶牛清洗乳房之前不能挤奶。我们把这些工作称为完成本项工作的准备工作。至少有一项杂务不要求有准备工作,这个可以最早着手完成的工作,标记为杂务 \(1\)。
John 有需要完成的 \(n\) 个杂务的清单,并且这份清单是有一定顺序的,杂务 \(k(k\gt 1)\) 的准备工作只可能在杂务 \(1\) 至 \(k−1\) 中。
写一个程序依次读入每个杂务的工作说明。计算出所有杂务都被完成的最短时间。当然互相没有关系的杂务可以同时工作,并且,你可以假定 John 的农场有足够多的工人来同时完成任意多项任务。
【输入】
第 \(1\) 行:一个整数 \(n(3\le n\le 10,000)\),必须完成的杂务的数目;
第 \(2\) 至 \(n+1\) 行,每行有一些用空格隔开的整数,分别表示:
- 工作序号(保证在输入文件中是从 \(1\) 到 \(n\) 有序递增的);
- 完成工作所需要的时间 \(len(1\le len\le 100)\);
- 一些必须完成的准备工作,总数不超过 \(100\) 个,由一个数字 \(0\) 结束。有些杂务没有需要准备的工作只描述一个单独的 \(0\)。
保证整个输入文件中不会出现多余的空格。
【输出】
一个整数,表示完成所有杂务所需的最短时间。
【输入样例】
7
1 5 0
2 2 1 0
3 3 2 0
4 6 1 0
5 1 2 4 0
6 8 2 4 0
7 4 3 5 6 0
【输出样例】
23
【核心思想】
-
问题分析:给定 \(n\) 个杂务,每个杂务 \(k\) 有完成时间 \(len_k\) 和若干前置依赖(均在 \(1\) 到 \(k-1\) 中)。由于有无限工人,无依赖的杂务可并行执行。求完成所有杂务的最短时间,即求从起点到每个节点的最长路径。这是一个递推/拓扑序 DP 问题,关键在于利用输入顺序即为拓扑序的特性进行递推。
-
算法选择:
- 递推(Topological Order DP):利用题目保证的"杂务 \(k\) 的准备工作只可能在 \(1\) 至 \(k-1\) 中",即输入顺序就是拓扑序
- 最长路径 DP:\(dp[k] = len_k + \max\{dp[pre]\}\),其中 \(pre\) 为 \(k\) 的所有前置杂务
-
关键步骤:
- 初始化:读取 \(n\)(杂务总数)
- 递推处理每个杂务(按输入顺序,即拓扑序):
- 读取杂务编号 \(key\)、耗时 \(t\)、第一个前置 \(flag\)
- 设置初始值:\(thing[key] = t\)(仅完成自身所需时间)
- 遍历所有前置依赖(直到读到 \(0\)):
- 更新 \(thing[key] = \max(thing[key], t + thing[flag])\),即取所有前置路径中的最大完成时间
- 更新全局答案 \(ans = \max(ans, thing[key])\)
- 读取下一个前置 \(flag\)
- 输出答案 \(ans\)(所有杂务完成的最短时间)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times m)\),其中 \(m \leq 100\) 为每个杂务的前置数量上限,总操作数不超过 \(n \times 100\)
- 空间复杂度:\(O(n)\),存储每个杂务的最早完成时间
-
递推与拓扑序 DP 的核心思想:
- 拓扑序保证无后效性:由于依赖只指向编号更小的杂务,按输入顺序处理时所有前置状态已计算完毕
- 最长路径模型:并行执行意味着某杂务的开始时间取决于其所有前置中最晚完成的那个,因此是取最大值而非求和
- 状态设计:\(thing[k]\) 表示完成杂务 \(k\) 及其所有前置链的最早时间,即节点 \(k\) 的最长路径长度
- 贪心并行性:无限工人意味着无需考虑资源冲突,只需关注依赖关系
- 适用于 AOE 网关键路径、任务调度、带依赖的并行处理类问题
【解题思路】

【算法标签】
普及 #递推
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, key, t, flag, ans=0, thing[10005];
int main()
{
cin >> n; // 输入n
while (n--) { // 循环n次
cin >> key >> t >> flag; // 记录序号,完成所需时间,以及杂务序号
thing[key] = t; // 完成当前杂务需时t
while (flag!=0) { // 当flag不为0,继续输入
thing[key] = max(thing[key], t+thing[flag]); // 将前一个杂务所需时间与当前耗时相加,并与当前耗时相比,求最大值。
ans = max(ans, thing[key]); // 当前杂务所需的最大时间,还需要和全部杂务的最大时间进行比较,找出最大值(其实就是最小值)
cin >> flag; // 继续输入
}
}
cout << ans; // 输出结果
return 0;
}
【运行结果】
7
1 5 0
2 2 1 0
3 3 2 0
4 6 1 0
5 1 2 4 0
6 8 2 4 0
7 4 3 5 6 0
23
浙公网安备 33010602011771号