关键路径

题1:问题 A: 关键路径

http://codeup.cn/problem.php?cid=100000624&pid=0
题目描述

  网的源点是入度为0的顶点,汇点是出度为0的顶点。网的关键路径是指从源点到汇点的所有路径中,具有最大路径长度的路径。上图中的关键路径为a->c->d->e,其权值之和为关键路径的长度为15。
  本题的要求是根据给出的网的邻接矩阵求该网的关键路径及其长度。

输入

  第一行输入一个正整数n(1<=n<=5),其代表测试数据数目,即图的数目

  第二行输入x(1<=x<=15)代表顶点个数,y(1<=y<=19)代表边的条数

  第三行给出图中的顶点集,共x个小写字母表示顶点

  接下来每行给出一条边的始点和终点及其权值,用空格相隔,每行代表一条边。
输出

  第一个输出是图的关键路径(用给出的字母表示顶点, 用括号将边括起来,顶点逗号相隔)

  第二个输出是关键路径的长度

  每个矩阵对应上面两个输出,两个输出在同一行用空格间隔,每个矩阵的输出占一行。

样例输入1 Copy

2
5 6
abcde
a b 3
a c 2
b d 5
c d 7
c e 4
d e 6
4 5
abcd
a b 2
a c 3
a d 4
b d 1
c d 3
样例输出1 Copy

(a,c) (c,d) (d,e) 15
(a,c) (c,d) 6

样例输入2 Copy

1
5 5
abcde
a b 5
a c 6
b d 7
c d 11
e c 10
样例输出2 Copy

(e,c) (c,d) 21

思路

先求点,再夹边
(1)按拓扑序和逆拓扑序分别计算各顶点(事件)的最早发生时间和最迟发生时间
最早(拓扑序): ve[j]=max{ve[i]+ length[i ->j] |边i->j存在}
最迟(逆拓扑序): vl[i]=min{vl[j] - length[i ->j] |边i->j存在}
(2)用上面的结果计算各边(活动)的最早开始时间和最迟开始时间
最早: e [ i->j] = ve[i]
最迟: l [ i->j ] = vl [j] – length[ i->j ]
(3)e [ i->j ] = l [ i->j ] 的活动即为关键活动。

步骤

(1)求点

(1.1)求事件j的最早发生时间ve[j](靠前序i1~ik)

  首先,有K 个事件Vi1 ~ Vik 通过活动ar1 ~ ark到达事件Vj ;
  活动的边权为length[r1] ~ length[rk]。
  假设已经算好了事件Vi1 ~ Vik的最早发生时间 ve [i1] ~ ve[ik];

  想要获得veLi]的正确值,它的前序 ve[il] ~ ve[ik] 必须已经得到
  在访问某个结点时保证它的前驱结点都已经访问完毕可以用拓扑排序实现
  在拓扑排序访问到结点Vi 时,不是让它去找前驱结点来更新ve[i], 而是使用ve[i]去更新其所有后继结点的ve值。通过这个方法,让拓扑排序访问到Vj 的时候, V i1 ~ Vik一定都已经用来更新过ve[j],此时的ve[j]便是正确值, 就可以用它去更新Vj的所有后继结点的ve值。
  事件Vj 的最早发生时间就是ve[i1] + length[r1] ~ ve[ik] + length[rk]中的最大值。

bool topologicalSort() {//拓扑排序
    queue<int>q;
    for (int i = 0; i<x; i++)
        if (inDegree[vertices[i]]==0){

            affair.push_back(vertices[i]);
            q.push(vertices[i]);
        }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        topOrder.push(u);//将u加入拓扑序列
        for (map<int, int>::iterator it = edge[u].begin(); it != edge[u].end(); it++) {
            int v = it->first, w = it->second;
            if (--inDegree[v] == 0){

                affair.push_back(v);
                q.push(v);
            }

            //用ve[u] 来更新u 的所有后继结点V
            if (ve[u] + w>ve[v])//当前事件的最早开始时间+下一个活动的时间>下一个事件的最早开始时间
                ve[v] = ve[u] + w;//下一个事件的最早开始时间取最大值
        }
    }
    if (topOrder.size() == x)
        return true;
    else
        return false;
}

(1.2)求事件i的最迟发生时间vl [i](靠后序j1~jk)

  同理,从事件Vi 出发通过相应的活动ar1 ~ ark可以到达K 个事件vj1 ~Vjk;
  活动的边权为length[r1] ~ length[ rk] 。
  假设已经算好了事件vj1 ~ vjk 的最迟发生时间vl [j1] ~vl [jk];

  如果需要算出vl [i]的正确值,它的后序 vl [j1] ~ vl [jk]必须已经得到。
  在访问某个结点时保证它的后继结点都已经访问完毕可以通过使用逆拓扑序列来实现。
  在上面实现拓扑排序的过程中使用了栈来存储拓扑序列,那么只需要按顺序出栈就是逆拓扑序列。
  而当访问逆拓扑序列中的每个事件Vi 时,就可以遍历Vi 的所有后继结点Vj1 ~ Vjk, 使用vl [j1] ~ vl [jk]来求出vl [i]。
  事件vi 的最迟发生时间就是vl [j1] - length[r1] ~ vl [jk] - length[rk] 中的最小值。

void retopologicalSort() {//逆拓扑序列就是栈结构的拓扑序列的出栈
    while (!topOrder.empty()) {
        int u = topOrder.top();
        topOrder.pop();
        for (map<int, int>::iterator it = edge[u].begin(); it != edge[u].end(); it++) {
            int v = it->first, w = it->second;
            //用u 的所有后继结点v 的vl 值来更新vl[u]
            if (vl[v] - w<vl[u])
                vl[u] = vl[v] - w;
        }
    }
}

(2)求边,找关键活动

最早: e [ i->j] = ve[i]
最迟: l [ i->j ] = vl [j] – length[ i->j ]
e [ i->j ] = l [ i->j ] 的活动即为关键活动。

//关键路径,不是有向无环图返回-1,否则返回关键路径长度
void CriticalPath() {
    map<char,char>path;
    fill(ve, ve + maxv, 0);
    if (!topologicalSort())//求ve[]数组(求点1)
        return;//不是有向无环图,返回-1
    int maxh = 0;//汇点就是最晚开始的点,那个点就是所有路径的终点,在其之后没有别的点了即没有边了即出度为0
    for (int i = 0; i<x; i++)
        if (ve[vertices[i]]>maxh)
            maxh = ve[vertices[i]];
    fill(vl, vl + maxv, maxh);//vl 数组初始化,初始值为汇点的ve 值

    retopologicalSort();//直接使用topOrder 出栈即为逆拓扑序列,求解vl 数组(求点2)

    //遍历邻接表的所有边, 计算活动的最早开始时间e和最迟开始时间1(求边,找关键活动)
    for (int i = 0; i<x; i++) {
        int u = vertices[i];//所有顶点和其出度包含了所有的边
        for (map<int, int>::iterator it = edge[u].begin(); it != edge[u].end(); it++) {
            int v = it->first, w = it->second;
            int e = ve[u], l = vl[v] - w;//活动的最早开始时间e和最迟开始时间l
            if (e == l)//如果e==l, 说明活动u->v 是关键活动
                path[u + 'a']=v + 'a';
//                printf("(%c,%c) ", u + 'a', v + 'a'); //输出关键活动
        }
    }
    for(int i=0; i<affair.size(); i++) {
        char u=affair[i]+'a';
        if(path.find(u)!=path.end()) {
            map<char,char>::iterator it=path.find(u);
            printf("(%c,%c) ", it->first, it->second); //输出关键活动
        }
    }
    printf("%d\n", maxh);
    affair.clear();
}

注意

  找到关键活动后不宜直接输出,因为直接输出的关键活动可能会缺乏前后逻辑性
例:
1
5 5
abcde
a b 5
a c 6
b d 7
c d 11
e c 10

若是直接输出,则为(c,d)(e,c),缺乏逻辑性

  所以保留下关键活动为map<char,char>path;
    保留下活动的先后序关系vector< int >affair;
  根据活动的先后序输出关键活动,才能确保逻辑性:(e,c) (c,d) 21

AC代码

#include<bits/stdc++.h>
using namespace std;
const int maxv = 50;
int n, x, y;
int vertices[maxv];//点集
map<int, int>edge[maxv];//边集
int inDegree[maxv], ve[maxv], vl[maxv];//入度情况,事件的最早/最迟发生时间
stack<int>topOrder;//拓扑序列
vector<int>affair;//事件:所有顶点的先后序关系,用于输出关键活动时是根据先后关系输出的
void init() {
    for (int i = 0; i < maxv; i++) {
        edge[i].clear();
        vertices[i] =inDegree[i]= 0;
    }
}
bool topologicalSort() {//拓扑排序
    queue<int>q;
    for (int i = 0; i<x; i++)
        if (inDegree[vertices[i]]==0) {

            affair.push_back(vertices[i]);
            q.push(vertices[i]);
        }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        topOrder.push(u);//将u加入拓扑序列
        for (map<int, int>::iterator it = edge[u].begin(); it != edge[u].end(); it++) {
            int v = it->first, w = it->second;
            if (--inDegree[v] == 0) {

                affair.push_back(v);
                q.push(v);
            }

            //用ve[u] 来更新u 的所有后继结点V
            if (ve[u] + w>ve[v])//当前事件的最早开始时间+下一个活动的时间>下一个事件的最早开始时间
                ve[v] = ve[u] + w;//下一个事件的最早开始时间取最大值
        }
    }
    if (topOrder.size() == x)
        return true;
    else
        return false;
}
void retopologicalSort() {//逆拓扑序列就是栈结构的拓扑序列的出栈
    while (!topOrder.empty()) {
        int u = topOrder.top();
        topOrder.pop();
        for (map<int, int>::iterator it = edge[u].begin(); it != edge[u].end(); it++) {
            int v = it->first, w = it->second;
            //用u 的所有后继结点v 的vl 值来更新vl[u]
            if (vl[v] - w<vl[u])
                vl[u] = vl[v] - w;
        }
    }
}
//关键路径,不是有向无环图返回-1,否则返回关键路径长度
void CriticalPath() {
    map<char,char>path;
    fill(ve, ve + maxv, 0);
    if (!topologicalSort())//求ve[]数组(求点1)
        return;//不是有向无环图,返回-1
    int maxh = 0;//汇点就是最晚开始的点,那个点就是所有路径的终点,在其之后没有别的点了即没有边了即出度为0
    for (int i = 0; i<x; i++)
        if (ve[vertices[i]]>maxh)
            maxh = ve[vertices[i]];
    fill(vl, vl + maxv, maxh);//vl 数组初始化,初始值为汇点的ve 值

    retopologicalSort();//直接使用topOrder 出栈即为逆拓扑序列,求解vl 数组(求点2)

    //遍历邻接表的所有边, 计算活动的最早开始时间e和最迟开始时间1(求边,找关键活动)
    for (int i = 0; i<x; i++) {
        int u = vertices[i];//所有顶点和其出度包含了所有的边
        for (map<int, int>::iterator it = edge[u].begin(); it != edge[u].end(); it++) {
            int v = it->first, w = it->second;
            int e = ve[u], l = vl[v] - w;//活动的最早开始时间e和最迟开始时间l
            if (e == l)//如果e==l, 说明活动u->v 是关键活动
                path[u + 'a']=v + 'a';
//                printf("(%c,%c) ", u + 'a', v + 'a'); //输出关键活动
        }
    }
    for(int i=0; i<affair.size(); i++) {
        char u=affair[i]+'a';
        if(path.find(u)!=path.end()) {
            map<char,char>::iterator it=path.find(u);
            printf("(%c,%c) ", it->first, it->second); //输出关键活动
        }
    }
    printf("%d\n", maxh);
    affair.clear();
}
int main() {
    char a, b;
    int t;
    scanf("%d", &n);
    while (n--) {
        init();
        scanf("%d %d", &x, &y);
        getchar();
        for (int i = 0; i<x; i++) {
            scanf("%c", &a);
            vertices[i] = a - 'a';
        }

        for (int i = 0; i<y; i++) {
            getchar();
            scanf("%c %c %d", &a, &b, &t);
            edge[a - 'a'][b - 'a'] = t;
            inDegree[b - 'a']++;
        }
        CriticalPath();
    }
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章