hihocoder 1343 : Stable Members【拓扑排序】
hihocoder #1343:題目
解釋:
一個學習小組,一共有N個學員,一個主管。每個學員都有自己的導師(一個或者多個),導師可以是其他學員也可以是主管。
每周學員都要把自己的學習報告和收到的報告提交給自己的導師,這個團隊設計很合理,沒有回環(投遞出去的報告不會回到自己手中),并且所有的報告最終都會投遞到主管那里。
但這個團隊中有的學員會因為其他某個學員不工作而導致報告無法提交到主管手中,我們稱這種學員為不可靠的。而不受某個學員不工作而影響到報告提交的就是可靠學員。
問題就是找出可靠學員的數量。
輸入:
第一行數字是N,學員總數。接下來每行對應1到N學員的導師數量和編號,例如第二行輸入(1 0),代表學員1的導師有1個,并且就是主管(0代表主管);第四行輸入(2 1 2),代表學員3的導師有兩個,分別是學員1和2。
輸出:
可靠學員的數量
題意:一個有向無環圖,最上游的點只有一個。若刪掉一個點,則某些后續點無法與最上游的點連通,則這些后續點為unstable的。要找出所有unstable的點,然后輸出剩下的stable點的數量。
解法一:BFS拓撲
對于每個頂點v,都遍歷其后續頂點,找到所有的unstable的點。具體方法如下,采用染色的方法:
對于某個頂點v,采用拓撲排序的方法遍歷其后續頂點,并依次染色。后續的頂點進入隊列的條件是,其所有的父頂點都已經被染成頂點v的顏色。因為會出現這樣的情況,當刪掉1時,雖然4的入邊有2條,但也是unstable的。
解法二: 記憶化搜索DFS【找每個學員的匯聚點】
思考一下,針對不穩定的學員,他的導師路徑如果有多條必定會在某個時刻匯聚到同一個學員那里,而穩定的學員匯聚點肯定是自身。
解釋:首先對于直接導師中就有主管的,那么匯聚點就是本身,因為本身就是穩定的。其次對于導師只有一個但不是主管的,迭代去找最靠近主管的匯聚點。最后對于有多個導師的情況,就需要分別迭代去找其導師的匯聚點,如果其某兩個導師的匯聚點不同,那么他是穩定的,他的匯聚點是自己。如果都一樣,那么匯聚點就是其導師們的匯聚點。
如樣例輸入中:
1、2的導師都是0,所以匯聚點是自己1與2。
3的導師有兩個1和2,他們的匯聚點分別是1,2,不同,那么3的匯聚點是3。
4的導師是3,3的匯聚點是3,那么4的匯聚點也是3。
5的導師4和3,匯聚點都是3,所以5的匯聚點也是3。
解法三:支配樹【必經節點,LCA】
這里用編號0來表示Master。此時,“不穩定成員”的定義就是:如果存在某一個編號不為0的點,使得從點0到該點的所有路徑中都必須經過這個點,那么該點代表的成員就是“不穩定成員”。上圖中,從點0到點4的所有路徑必經過點3,因此成員4是不穩定成員。
支配樹:
??? 將上面“不穩定成員”的定義加以拓展,去掉“非0點”的限制,可以得到“支配點”的定義,即:對于某一個目標點,如果存在一個點,使得圖中從起點到目標點所有路徑都必須經過該點,那么該點就是目標點的“支配點”。上圖中:點1、2、3、4的支配點分別為:0、0、0、0和3。顯然,如果從起點出發可以到達圖中的所有點,那么起點就是圖中所有點的“支配點”。
??? 一個點的“支配點”不一定只有一個。例如:如果對于某個點,從起點到該點的路徑只有1條,那么該路徑上的所有點都是該點的支配點。對于有多個支配點的情況,我們可以找到一個支配點,它距離目標點的最近,這個點我們成為“直接支配點”。對于給定的圖,一個點可能用有多個支配點,但它的直接支配點一定是唯一的。此時,出去起點外,所有的點都有自己唯一的“直接支配點”。
??? 而“支配樹”是這樣的一種多叉樹:圖中的點對應于樹的節點,而每一個節點的父節點則是它的直接支配點。上文中的圖構成的支配樹如下:
??? 顯然,完成樹的構建后,每個點的父節點就是它的直接支配點,而這個點的所有祖先節點都是它的支配點。此時,根據題意,我們要找的“穩定成員”就是直接支配點是0號點(Master)的成員,也就是支配樹中根節點的孩子。
·建樹:
??? 為了建立支配樹,就必須知道每個點的直接支配點。考慮原圖中每個點的“前驅點”,本題中即考慮每個成員的mentor。如果某個成員只有一個mentor,那么顯然從Master到該成員的路徑一定都會經過他的mentor,因此mentor就是該成員的直接支配點;對于抽象的圖而言,如果某一個點只有一個前驅點,那么該前驅點就是當前點的直接支配點;如果某個成員有多個mentor,那么對于某一個mentor而言,從Master到該成員就未必會經過它,所以,當某個成員擁有多個mentor時,他的mentor都不是他的直接支配點;對于抽象的圖而言,如果一個點有多個前驅,那么這些前驅點都不是它的直接支配點,我們需要考慮這些前驅節點的支配點,當這些前驅節點擁有共同的一個支配點時,說明從起點到這些前驅點的所有路徑必會經過這個共有的支配點,也就是說,從起點到目標點的所有路徑都會經過這個共有的支配點,因此這個共有的支配點就是目標點的直接支配點。這個結論對于只有一個前驅節點的情況也使用
??? 根據支配樹的定義,多個節點共有的支配點是明確的,就是他們的公共祖先,而我們要找的則是最近公共祖先。這個結論對于只有一個前驅節點的情況也使用,因為如果只有一個點,那么它的最近公共祖先就是它自己。
??? 于是,建立支配樹的過程就是:首先將起點加入到樹中,作為整個支配樹的根,然后對于每一個節點,找到其所有前驅節點在支配樹中的最近公共祖先,這個祖先節點就是當前節點的父節點。
·拓撲排序:
??? 上面的建樹過程有一個條件必須要保證,即某個點要加入到樹中時,必須確保它的所有前驅點已經在樹中,這樣才可以找到這些點的最近公共祖先。因此,節點添加入樹中的順序是很重要的。我們可以通過拓撲排序找到合適的順序,拓撲排序的結果就是節點加入樹的順序。這樣就保證了前驅節點一定先于當前節點加入到樹中。
·最近公共祖先:
??? 建樹的過程設計到了求多個節點的最近公共祖先。我們可以采用一種復雜度為O(lgn)的算法來求解它。考慮每個節點在樹中的高度,將高度小的節點沿著父節點指針向上移動,在所有節點的高度相同時再同時沿著父節點指針向上移動,當所有的節點都到達同一個節點時,這個終點就是這些節點的最近公共祖先。
??? 以上是本題的解答思路,完成建立支配樹后,統計一下根節點有多少個孩子,就是本題的答案。
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<vector> using namespace std; const int maxn = 100005; struct Edge { int to,next; }edge[maxn*10]; int n,cnt,ans,head[maxn],deg[maxn]; int dep[maxn],parent[maxn],tmp[15]; //tmp[i]表示第i個直接前驅回溯到的節點 vector<int> g[maxn]; void addedge(int u,int v) { edge[cnt].to = v; edge[cnt].next = head[u]; head[u] = cnt++; } int LCA(int u) { int min_dep = -1; for(int i = 0; i < g[u].size(); i++) { int v = g[u][i]; tmp[i] = v; if(min_dep == -1 || min_dep > dep[v]) min_dep = dep[v]; } for(int i = 0; i < g[u].size(); i++) { while(dep[tmp[i]] > min_dep) tmp[i] = parent[tmp[i]]; } while(true) { int i; for(i = 1; i < g[u].size(); i++) if(tmp[i] != tmp[0]) break; if(i >= g[u].size()) break; for(int i = 0; i < g[u].size(); i++) tmp[i] = parent[tmp[i]]; } return tmp[0]; } void bfs() { queue<int> q; for(int i = 0; i <= n; i++) if(deg[i] == 0) q.push(i); while(!q.empty()) { int u = q.front(); q.pop(); for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; deg[v]--; if(deg[v] == 0) { parent[v] = LCA(v); dep[v] = dep[parent[v]] + 1; if(parent[v] == 0) ans++; q.push(v); } } } } int main() { while(scanf("%d",&n)!=EOF) { memset(head,-1,sizeof(head)); memset(deg,0,sizeof(deg)); for(int i = 0; i <= n; i++) g[i].clear(); for(int i = 1; i <= n; i++) { int k; scanf("%d",&k); for(int j = 1; j <= k; j++) { int u; scanf("%d",&u); addedge(u,i); //childg[i].push_back(u); //parentdeg[i]++; //in degree} } ans = 0; bfs(); printf("%d\n",ans); } return 0; }轉載于:https://www.cnblogs.com/demian/p/6536799.html
總結
以上是生活随笔為你收集整理的hihocoder 1343 : Stable Members【拓扑排序】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 四则运算关于加括号的思路
- 下一篇: bzoj 2342: 双倍回文 回文自动