試題 算法提高 士兵排隊問題
資源限制
時間限制:1.0s 內存限制:256.0MB
試題
有N個士兵(1≤N≤26),編號依次爲A,B,C,…,隊列訓練時,指揮官要把一些士兵從高到矮一次排成一行,但現在指揮官不能直接獲得每個人的身高信息,只能獲得“P1比P2高”這樣的比較結果(P1、P2∈A,B,C,…,Z,記爲 P1>P2),如”A>B”表示A比B高。
請編一程序,根據所得到的比較結果求出一種符合條件的排隊方案。
(注:比較結果中沒有涉及的士兵不參加排隊)
輸入要求
比較結果從文本文件中讀入(文件由鍵盤輸入),每個比較結果在文本文件中佔一行。
輸出要求
若輸入數據無解,打印“No Answer!”信息,否則從高到矮一次輸出每一個士兵的編號,中間無分割符,並把結果寫入文本文件中,文件由鍵盤輸入:
樣例輸入
A>B
B>D
F>D
樣例輸出
AFBD
題解
本題是拓撲排序的裸題。
1.如何轉換爲有向圖?
將 > 號看成 -> ,表示一個結點指向另一個結點,如 A>B ,代表A指向B,同理 A<B 代表B指向A。
2.如何將A-Z 字母轉換爲結點?
可以把 A 看成第0個結點,那麼B的編號就是 1 … … 所以Z的編號爲25 。
3.如何實現拓撲排序?
方法上可以使用bfs 和dfs ,這裏我用的是dfs 。dfs深搜每個結點返回的過程實際上就是拓撲排序的逆序(好好思考一下)。
怎麼判斷該圖是否爲DAG(有向無環圖),對這道題來講,也就是什麼時候輸出 no answer 。若採用 bfs ,則判斷最後隊列中是否還有元素,如果隊列中有元素,那麼這些結點間存在環,不存在拓撲排序;採用dfs ,若在遞歸深搜中如果發現某個節點之前已經訪問過,而且還未遞歸返回,那麼就存在環。
4.vis[ i ] 的三種狀態
.vis[i]=1 代表結點i訪問過了,並且已經遞歸回來了
.vis[i]=0 代表結點i未訪問過
.vis[i]=-1 代表結點i訪問過,但是還未遞歸回來
#include<bits/stdc++.h>
using namespace std;
struct edge {
int from; //出邊
int to; //入邊
edge(int _from, int _to) {
from = _from;
to =_to;
}
};
vector<edge> G[26]; //一共26個結點
int vis[26];
stack<char> S; //將得到的逆序拓撲排序正序
int dfs(int vertex) { //vertex爲頂點編號
for (int i = 0; i < G[vertex].size(); i++) {
int v2 = G[vertex][i].to;
if (vis[v2]==0) { //還未訪問過
vis[v2] = -1;
if (dfs(v2))
return 1;
}
else if (vis[v2] == -1) { //存在環,不存在拓撲排序
return 1; //立刻返回,終止遞歸
}
}
vis[vertex] = 1; //它的所有後代都已經遞歸返回了,那麼更新它爲 1
S.push('A' + vertex); //利用棧,由逆序得到答案
return 0;
}
int main() {
fill(vis, vis + 26, 1); //26個結點初始爲1,代表都已經訪問過了,
char a[5]; //下面輸入的時候,出現的結點才更新爲0
int x, y;
while (fgets(a,5,stdin)&&a[0]!=EOF) {
x = a[0] - 'A'; y = a[2] - 'A'; //存儲邊
vis[x] = vis[y] = 0; //0代表這些邊需要考慮
if (a[1] == '>')
G[x].push_back(edge(x, y));
else
G[y].push_back(edge(y, x));
}
for (int i = 0; i < 26; i++) {
if (vis[i]==0) {
vis[i] = -1; //代表正在遞歸訪問,未返回
if (dfs(i)) {
printf("No Answer!");
exit(0);
}
}
}
while (!S.empty()) {
printf("%c", S.top());
S.pop();
}
return 0;
}