關鍵路徑

題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;
}


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