题目:生产口罩
链接:http://class.51nod.com/Classes/Problem.html#courseProblemId=1718&classId=129
//注:题目来自51nod
这道题是一道集拓补排序和DP的好题,而且还有几个细节值得注意,先讲大体思路,再讲细节。
题目:
在这套方案里,有n个自动化工厂,分别对应着生产口罩的不同工序。不过,一些自动化工厂要开始进行自己的工序,必须要先等待另外一些工厂完成自己的工序,我们把这些工序称为前置工序。
每个工厂完成自己的工序都需要一定的时间,且只有所有工厂都完成自己的工序,口罩生产才能完成。
现在告诉你每个工厂完成工序需要的时间,以及每个工厂进行自己工序需要的前置工序,问最早什么时候口罩生产才能完成
思路:
实际上所谓的“前置工序”实际上就是有向边,一个工厂是一个节点,整个方案就是一个有向图
所以这不就是套拓补排序模板吗
在此题中我们的目的是求出最短时间,所以对于每一个点,我们都需要求出Ta的最短时间。但怎么求呢?
对于每一个点都有很多个入边,因此我们只需要求出Ta所有入边的最短时间,在选择一个最长的加上本点的时间即可。
代码实现:
我们都知道拓补排序的核心思路是每次删去一条边,然后判断此点是否被“解锁”,是则加入队列,但是如果我们删除了所有通向一个点A的边,那我们怎么才能求出最短时间呢?
我们可以设time[i]表示完成i点的任务需要时间,ans[i]表示从头到完成i点需要的总时间,fa表示通向i节点的节点,在删除之前,比较到底是ans[i](即以前求出的最长路经)大还是time[i]+ans[fa](从fa节点到i节点)大,选择更大的一个赋值给ans[i]即可。
细节方面:
第一个细节:从哪一个节点开始?
如图:
这种图像该从哪一个开始Bfs?
我目前看到的题解是说在Ta们(入度为零)前面再建立一个节点,并指向Ta们,如图
但是这样做可能会很麻烦,于是可以这么做,对于入度为零的节点,直接把Ta放在队列里,并以第一个入队的开始,这样就不会忽略一个节点了。
第二个细节:最终答案怎么算?
还是那张图:
如果按Bfs算的话最终结果一定是6,但是4也得算啊,那怎么办呢?
还是那个题解,Ta的想法是在这种情况下,在像4,6这样的节点后在加一个新节点,如图:
这就更麻烦了对不对?那怎么办呢?
实际上更简单,因为最短时间只会随着有向边逐渐增加,而不会减小,所以拓补排序结束以后我们就一个一个的遍历节点,找到最大的ans[]即可。
喜闻乐见的代码
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<string>
#define Max(a,b) (a) > (b) ? (a) : (b)
using namespace std;
int n,m;
int book[200010];
int enter[200001];
int head = 1,tail = 1;
int que[2000001];
long long ans[200001];
long long time[200001];
vector<int> G[200001];
void Bfs(int st){
while(head != tail){
int t = que[head];
for(int i = 0,len = G[t].size() ;i < len;i++){
enter[G[t][i]]--;
ans[G[t][i]] = Max(ans[G[t][i]],ans[t] + time[G[t][i]]);
if(enter[G[t][i]] == 0){
que[tail++] = G[t][i];
book[G[t][i]] += 1;
}
}
head++;
}
}
int main(){
scanf("%d%d",&n,&m);
int u,v;
for(int i = 1;i <= n;i++){
scanf("%lld",&time[i]);
ans[i] = time[i];
}
for(int i = 1;i <= m;i++){
scanf("%d%d",&u,&v);
G[u].push_back(v);
enter[v]++;
}
int t = -1;
for(int i = 1;i <= n;i++){
if(enter[i] == 0){
t = i;
que[tail++] = i;
}
}
Bfs(t);
long long res = -99999999;
for(int i = 1;i <= n;i++){
res = Max(res,ans[i]);
}
printf("%lld",res);
return 0;
}
本文到这里结束了,感谢阅读。