P1113 杂务 题解

题目传送门

一、拓扑排序完整代码(bfs)

#include <bits/stdc++.h>

using namespace std;
const int N = 10010;
int n;      //必须完成的杂务的数目
int x;      //工作序号
int y;      //一些必须完成的准备工作
int ans;    //最终结果
int a[N]; //完成工作所需要的时间a[x]
int f[N]; //这个结点的最长时间

vector<int> edge[N]; //出边链表
int ind[N]; //入度
queue<int> q; //队列
/**
测试数据:
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
*/

int main() {
    //需要完成的杂务的数目
    cin >> n;
    //创建DAG
    for (int i = 1; i <= n; i++) {
        cin >> x >> a[x];
        while (cin >> y) {
            if (!y) break;
            //y是前序结点
            edge[y].push_back(x);//y结点出发到达x结点的边,所以x结点的入度++
            ind[x]++;//维护入度
        }
    }
    //步骤一:初始化队列,将入度为 0 的节点放入队列。
    for (int i = 1; i <= n; i++) {
        if (ind[i] == 0) {//如果入度为0,则为DAG的入口
            q.push(i);//入队列
            f[i] = a[i];//初始值,动态规划的base case
        }
    };
    //拓扑排序
    while (!q.empty()) {
        int p = q.front();
        q.pop();
        //步骤二:取出队首,遍历其出边,删除出边,将能够到达的点入度减一,同时维护答案数组。
        for (int i = 0; i < edge[p].size(); i++) {
            int y = edge[p][i];
            ind[y]--;
            if (ind[y] == 0) q.push(y);  //步骤三:若在此时一个点的入度变为 0,那么将其加入队列。
            //看看能不能获取到更大的时长
            f[y] = max(f[y], f[p] + a[y]);
        }
    }
    //统计答案
    for (int i = 1; i <= n; i++) ans = max(ans, f[i]);
    //输出大吉
    printf("%d
", ans);
    return 0;
}

二、拓扑排序完整代码(dfs)

dfs本质是递归,如果从每个点发出进行深度优先,可以理解为自顶向下求以每个结点为出发点的最长链。为防止重复计算,可以使用记忆化搜索的办法对结果进行保存,已经探索完成的结点,不必重复进行搜索。

比如,从1号结点出发,1号有两个出边,分别到达2和4,那么,在求解1号结点的最长链过程中,肯定是计算完毕2和4的,(否则1也算不出来啊),那么2和4需要再次计算时,就没必要算了,把结果直接给上就行了。这里需要特别说明的是:P260的图18-12绘制的含义,与代码的不尽一致!最上面的那个结点,最终不是像书中一样填写的是1,而是填写的是20,可以跟踪一下代码就明白了,vis[1]=20!如果看完那张图,再看代码,就会糊涂,就是因为它们两个表达的意思是反着的。

#include <bits/stdc++.h>

using namespace std;
const int N = 10010;
int n;      //必须完成的杂务的数目
int x;      //工作序号
int y;      //一些必须完成的准备工作
int ans;    //最终结果
int a[N]; //完成工作所需要的时间a[x]
int f[N]; //这个结点的最长时间
vector<int> edge[N]; //出边链表

/**
测试数据:
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
*/
/**
 * 功能:计算x号任务的最短完成时间
 * @param x
 * @return
 */
 /**
  总结:
  1、创建DAG的通用办法
  2、注意谁是出发点,谁是终止点
  3、深度优先搜索的应用,万能的魔法函数
  4、记忆化搜索优化深度优先搜索
  5、计算的是每个结点出发的最长链(自顶向下)
  */
int dfs(int x) {
    //记忆化搜索,防止重复计算
    if (f[x]) return f[x];
    //找到连接到这个结点的边的最长的一个
    for (int i = 0; i < edge[x].size(); i++)
        f[x] = max(f[x], dfs(edge[x][i]));
    //加上本号任务的时长
    f[x] += a[x];
    //返回最短时间
    return f[x];
}

int main() {
    //创建DAG的标准套路
    //需要完成的杂务的数目
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> x >> a[x]; //完成工作所需要的时间len[x],注意:此处书中写的是len[i],也是可以AC的,
        // 原因是默认输入就是1,2,3...这样的输入,没有乱序输入。其实,如果没有乱序输入,
        // 这个cin>>x就是无用的,因为x肯定是等于i的。
        while (cin >> y) {
            //需要完成的准备工作
            if (!y) break;  //由一个数字0结束
            //前序啊!注意,这里是前序是谁!!!由谁到谁有一条有向边!!!
            edge[y].push_back(x); //这里要注意!!!! 是y向x有一条有向边,描述的是y是x的前序工作 !!!!
        }
    }
    //以上代码建图完毕!!!!

    //对于每项任务,分别计算最长工作时长,取最大值,就是最后的答案
    for (int i = 1; i <= n; i++) ans = max(ans, dfs(i));
    //输出大吉
    cout << ans << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/littlehb/p/15122085.html