題目連結
1.有唯一拓撲序
2.存在多個拓撲序
3.有環(不存在拓撲序)
解法一:拓撲排序
時間複雜度\(O(M*(N+M))\)
題意
每次給定我們一個條件:\(X<Y\)
讓我們判斷何時能把所有數之間的關係明確的找出來
- 如果找出來了,提前結束,即後面即使出現了矛盾也不管了
- 如果出現了矛盾,提前結束
- 既不出現矛盾也找不出所有數之間的關係
思路
把\(A\)<\(B\)看做由\(A\)指向\(B\)的一條邊
每次加入一條邊就做一次拓撲排序,此時會有兩種情況:
- 所有邊都加入了拓撲佇列當中
- 有至少一條邊沒有加入佇列當中
對於情況2,顯然是出現了環,有環就說明出現了衝突(\(A<B\) && \(B<A\))
對於情況1,還有兩種情況: - 每個時刻中佇列都只有一個數
- 有至少一個時刻佇列中存在至少兩個數。這是一個坑點,想了一個多小時。我們下面詳細解釋。
先舉個例子:
- 對於\(A<B\),\(A<C\),這兩個條件,我們是無法得出\(A,B,C\)之間的關係的,準確的來說是無法確定\(B\)和\(C\)之間的關係
- 對於\(A<B\), \(B<C\), \(D<E\)這三個條件,我們可以確定\(A,B,C\)之間的關係和\(D,E\)之間的關係,但是我們無法確定 \(A,B,C\)與\(D,E\)之間的關係
但是,上述兩個例子都是有拓撲序的。並且,都存在至少一個時刻,佇列至少存在兩個元素。 - 當\(A\)出隊時,\(B,C\)入隊,佇列中存在兩個元素
- 初始狀態時,\(A,D\)的入度為0,此時佇列中存在兩個元素
我們可以發現,只要某一時間佇列中存在多餘一個元素,就說明存在\(A<B\)和\(A<C\)的情況,對於情況2我們可以看做一個虛擬源點\(V\)同時\(<A\)和\(<D\)
總上,也就是說沒兩個點之間的關係必須唯一確定。
可以用 top_sort
判斷以下三種情況:
- 有唯一拓撲序
- 存在多個拓撲序
- 有環(不存在拓撲序)
#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]\) 必定都存在
兩個坑:
- 不可以提前 \(break\) 掉輸入,因為有多組資料,如果你提前 \(break\) 掉當前這一組資料的輸入,那麼該組資料的後續輸入會影響下一組資料
- \(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;
}