拓撲序的三種功能

光風霽月發表於2024-03-09

題目連結
1.有唯一拓撲序
2.存在多個拓撲序
3.有環(不存在拓撲序)

解法一:拓撲排序

時間複雜度\(O(M*(N+M))\)

題意

每次給定我們一個條件:\(X<Y\)
讓我們判斷何時能把所有數之間的關係明確的找出來

  1. 如果找出來了,提前結束,即後面即使出現了矛盾也不管了
  2. 如果出現了矛盾,提前結束
  3. 既不出現矛盾也找不出所有數之間的關係

思路

\(A\)<\(B\)看做由\(A\)指向\(B\)的一條邊
每次加入一條邊就做一次拓撲排序,此時會有兩種情況:

  1. 所有邊都加入了拓撲佇列當中
  2. 有至少一條邊沒有加入佇列當中
    對於情況2,顯然是出現了環,有環就說明出現了衝突(\(A<B\) && \(B<A\))
    對於情況1,還有兩種情況:
  3. 每個時刻中佇列都只有一個數
  4. 有至少一個時刻佇列中存在至少兩個數。這是一個坑點,想了一個多小時。我們下面詳細解釋。

先舉個例子:

  1. 對於\(A<B\),\(A<C\),這兩個條件,我們是無法得出\(A,B,C\)之間的關係的,準確的來說是無法確定\(B\)\(C\)之間的關係
  2. 對於\(A<B\), \(B<C\), \(D<E\)這三個條件,我們可以確定\(A,B,C\)之間的關係和\(D,E\)之間的關係,但是我們無法確定 \(A,B,C\)\(D,E\)之間的關係
    但是,上述兩個例子都是有拓撲序的。並且,都存在至少一個時刻,佇列至少存在兩個元素。
  3. \(A\)出隊時,\(B,C\)入隊,佇列中存在兩個元素
  4. 初始狀態時,\(A,D\)的入度為0,此時佇列中存在兩個元素
    我們可以發現,只要某一時間佇列中存在多餘一個元素,就說明存在\(A<B\)\(A<C\)的情況,對於情況2我們可以看做一個虛擬源點\(V\)同時\(<A\)\(<D\)
    總上,也就是說沒兩個點之間的關係必須唯一確定

可以用 top_sort 判斷以下三種情況:

  1. 有唯一拓撲序
  2. 存在多個拓撲序
  3. 有環(不存在拓撲序)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 30, M = 10100;

int n, m;
int h[N], e[M], ne[M], idx;
int deg[N], din[N];
int q[N];

void add(int a, int b) 
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

bool topsort(bool &flag)
{
    int hh = 0, tt = -1;
    
    for(int i = 0; i < n; i ++ )    din[i] = deg[i];
    
    for(int i = 0; i < n; i ++ ) 
        if(!din[i])
            q[ ++ tt ] = i;
    

    
    while(hh <= tt)
    {
        if(tt - hh >= 1)    flag = false;//不可以放在while迴圈的外面
        
        int t = q[hh ++ ];
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(-- din[j] == 0)  q[ ++ tt ] = j;
        }
    }
    
    return tt == n - 1;
}

void init()
{
    memset(h, -1, sizeof h);
    idx = 0;
    memset(din, 0, sizeof din);
    memset(deg, 0, sizeof deg);
}

int main()
{
    while(cin >> n >> m, n || m)
    {
        init();
        bool is_done = false;
        
        for(int i = 1; i <= m; i ++ )
        {
            string s;   cin >> s;
            int a = s[0] - 'A', b = s[2] - 'A';
            add(a, b);
            deg[b] ++ ;
            
            if(is_done) continue;//已經有結果或者結果矛盾了
            
            bool is_one_root = true;
            bool has_res = topsort(is_one_root);
            
            if(!has_res)//出現矛盾
            {
                printf("Inconsistency found after %d relations.\n",i);
                is_done = true;
            }
            else if(has_res && is_one_root)
            { 
                printf("Sorted sequence determined after %d relations: ",i);
                for(int i = 0; i < n; i ++ )    cout << char(q[i] + 'A');
                puts(".");
                is_done = true;
            }
        }
            
        if(!is_done)    puts("Sorted sequence cannot be determined.");
    }
    
    return 0;
}

解法二:Floyd演算法

時間複雜度:\(O(N^3)\)

思路:

每加入一條邊就用這條邊對所有邊做一次傳遞遞包操作
然後判斷是否存在自環,有自環就說明同時存在\(A<B\)\(B<A\)
傳遞遞包:
已知:\(A<B\)\(B<\)C 則:\(A<C\)

#include <cstdio>
#include <cstring>
#include <queue> 
#include <iostream>
using namespace std;
const int N = 26;
int n, m, t, d[N][N], g[N][N], in[N]; //有無向邊就代表關係出錯 若一個點能夠到達其他所有邊那麼排序完成 執行一次拓撲序即可 
char s[4];
void floyd() {
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                //i-->k有邊 且 k-->j有邊 那麼i-->j有邊 
                if (g[i][k] && g[k][j]) g[i][j] = 1;  
            }
        }
    }
}
void topsort() {
    queue<int> q;
    for (int i = 0; i < n; i++) if (!in[i]) q.push(i);
    printf("Sorted sequence determined after %d relations: ", t);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        printf("%c", (char)('A' + u)); 
        for (int v = 0; v < n; v++) {
            if (d[u][v]) { //查詢的時候我們應按照非閉包圖來求 
                in[v]--;
                if (!in[v]) q.push(v);
            }
        }
    }
    printf(".\n");
}
int main() {
    while (scanf("%d%d", &n, &m), n) {
        memset(g, 0, sizeof(g)); //0代表沒有邊 1代表有邊 、
        memset(d, 0, sizeof(d));
        memset(in, 0, sizeof(in));
        bool find = false, fail = false; //若為true代表找到一種序列 
        for (int j = 1; j <= m; j++) {
            scanf("%s", s);
            if (find || fail) continue; //就不用繼續查詢了 
            int u = s[0] - 'A'; //A < B 那麼建立一條A->B的邊 若出現雙向邊代表出錯 
            int v = s[2] - 'A';
            in[v]++;//入度增加 
            g[u][v] = d[u][v] = 1; //求一次floyd   
            floyd(); 
            //檢查一下
            find = true; //假定找到了 
            for (int u = 0; u < n; u++) {
                if (g[u][u]) { //代表出現了 A < A 
                    fail = true; //存在雙向邊 狀態出錯了 
                    break;
                }
                for (int v = 0; v < n; v++) {
                    //必須滿足 其中一個為0 一個為1 若2個都為0 0 或 1 1則不合法 
                    if (u != v && (!g[u][v] && !g[v][u])) find = false;//代表有一條邊不連通 
                }
            } 
            t = j; 
        }
        if (fail) printf("Inconsistency found after %d relations.\n", t);
        else if (find) topsort();
        else printf("Sorted sequence cannot be determined.\n");
    }
    return 0;
}

2024/3/9

思路:對於任意合法序列例如\(A<B<C<D(n=4)\)
\(A\) 的後面有 \(3\) 個比它大的,\(B\) 的後面有 \(2\) 個比它大的
\(C\) 的後面有 \(1\) 個比它大的,\(D\) 的後面沒有比它大的
因此我們可以設定一個陣列 \(st[i]\) 用來表示是否存在一點它滿足有 \(i\) 個比它大的數
如果存在這麼一個合法序列,那麼 \(st[0,1,...,n-1]\) 必定都存在

兩個坑:

  1. 不可以提前 \(break\) 掉輸入,因為有多組資料,如果你提前 \(break\) 掉當前這一組資料的輸入,那麼該組資料的後續輸入會影響下一組資料
  2. \(st\) 應該放在 \(check\) 裡面初始化而不是讀入一次新資料時初始化,因為可能存在同一點,它在不同時刻滿足 \(st[0]=true\), \(st[1]=true\) ....,而我們希望的是每個滿足 \(st[]=true\) 的點都互不相同

其實這樣做和透過 \(top_sort\) 來判斷本質上是一樣的,因為如果滿足我們想要的 \(st[0,1,...,n-1]=true\),其實就是滿足存在一個唯一的拓撲序。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100;

int n, m;
bool g[N][N];   // g[l][r] --> l < r
bool st[N];     // st[i]=true --> 存在某個數有i個比它大的數
char ch[N];

bool check()
{
    memset(st, false, sizeof st);   // // WRONG 3
    for(int i = 1; i <= n; i ++ )
    {
        int sum = 0;
        for(int j = 1; j <= n; j ++ )   sum += g[i][j];
        st[sum] = true;
        ch[sum] = 'A' + i - 1;
    }   
    int cnt = 0;
    for(int i = 0; i < n; i ++ )    cnt += st[i];
    return cnt == n;
}

void floyd()
{
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= n; j ++ )
            for(int k = 1; k <= n; k ++ )
                g[i][j] |= g[i][k] & g[k][j];
}

int main()
{
    while(cin >> n >> m, n || m)
    {
        // memset(st, false, sizeof st);    // WRONG 1
        memset(g, false, sizeof g);
        bool flag = false;
        for(int i = 1; i <= m; i ++ )
        {
            string s;   cin >> s;
            int l = s[0] - 'A' + 1, r = s[2] - 'A' + 1;
            if(flag)    continue;
            if(g[r][l] || l == r)
            {
                flag = true;
                printf("Inconsistency found after %d relations.\n", i);
                continue;
                // break;  // WRONG 2
            }
            g[l][r] = true;   
            floyd();
            if(check())
            {
                flag = true;
                printf("Sorted sequence determined after %d relations: ", i);
                for(int i = n - 1; i >= 0; i -- )    cout << ch[i];
                cout << "." << endl;
                continue;
                // break;  // (x)
            }
        }
        if(!flag)   puts("Sorted sequence cannot be determined.");
    }
    return 0;
}

相關文章