1. 程式人生 > >POJ 2186 - Popular Cows - 強連通分量,縮點

POJ 2186 - Popular Cows - 強連通分量,縮點

描述 res entry algo nbsp include truct 簡便 mem

題目大意:

給定一個含N個點、M條邊的有向圖,求其中有多少個點,可以由其他任意一點出發到達它?

N<=1e4,M<=5e4。

為了描述和編程簡便,我們建立原圖的反圖,這樣問題轉化為:有多少個點滿足從它出發可以到達其他任意一點。

若無特殊說明,以下所指的圖均為反圖

引理1:滿足條件的所有點必然在同一強連通分量內。

證明很簡單,如果它們不在同一強連通分量內,那麽其中必然有兩點x,y使得x→y的路徑不存在,與題目要求矛盾。

我們考慮求出該圖的所有強連通分量,然後對於每個強連通分量,檢驗從其中任一點出發,能否到達其他所有的點。

我們可以采取縮點的方法,將每個強連通分量縮成一個點。

引理2:縮點後的圖必然是一個有向無環圖(DAG)。

證明:如果縮點後存在環,那麽這個環可以合並成一個更大的強連通分量。這與強連通分量的極大性矛盾。

我們統計縮點後每個點的入度。如果有2個或以上的點入度為0,那麽它們是互相不可達的,此時答案為0。

否則,如果只有一個點入度為0,答案就是該點所代表的強連通分量的大小。

實現時可以借助兩個數組:sccId[x]表示點x所屬的強連通分量的編號,sccSize[x]表示點x所屬的強連通分量的大小。

參考:有向圖強連通分量的Tarjan算法

代碼:

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <algorithm>
  4
#include <stack> 5 6 #define FILLARR(arr, ch) memset(arr, ch, sizeof(arr)) 7 8 const int maxN = (int)1e4 + 5; 9 const int maxM = (int)5e4 + 5; 10 11 struct Edge 12 { 13 int to, next; 14 void assign(int t, int n) 15 { 16 to = t; 17 next = n; 18 }
19 }; 20 21 Edge elist[maxM]; 22 int head[maxN]; 23 int ecnt; 24 int N, M; 25 26 void initElist() 27 { 28 FILLARR(head, -1); 29 ecnt = 0; 30 } 31 32 inline void addEdge(int from, int to) 33 { 34 elist[ecnt].assign(to, head[from]); 35 head[from] = (ecnt++); 36 } 37 38 void input() 39 { 40 scanf("%d%d", &N, &M); 41 initElist(); 42 for (int u, v, i = 0; i < M; i++) 43 { 44 scanf("%d%d", &u, &v); 45 addEdge(v, u); 46 } 47 } 48 49 int dfn[maxN]; 50 int low[maxN]; 51 bool inStk[maxN]; 52 int sccId[maxN]; 53 int sccSize[maxN]; 54 int lastDfn = 0; 55 std::stack<int> stk; 56 57 inline void pushToStk(int v) 58 { 59 stk.push(v); 60 inStk[v] = true; 61 } 62 63 inline int popFromStk() 64 { 65 int v = stk.top(); 66 stk.pop(); 67 inStk[v] = false; 68 return v; 69 } 70 71 void dfs(int cur) 72 { 73 dfn[cur] = low[cur] = (++lastDfn); 74 pushToStk(cur); 75 76 for (int e = head[cur]; e != -1; e = elist[e].next) 77 { 78 int to = elist[e].to; 79 if (dfn[to] == 0) 80 { 81 dfs(to); 82 low[cur] = std::min(low[cur], low[to]); 83 } 84 else if (inStk[to]) 85 low[cur] = std::min(low[cur], dfn[to]); 86 } 87 88 if (dfn[cur] == low[cur]) 89 { 90 for (int v = popFromStk(); ; v = popFromStk()) 91 { 92 sccId[v] = cur; 93 sccSize[cur] += 1; 94 if (v == cur) 95 break; 96 } 97 } 98 } 99 100 int inDeg[maxN]; //compressed graph in which each SCC is compressed into one node 101 102 int solve() 103 { 104 for (int i = 1; i <= N; i++) 105 if (dfn[i] == 0) 106 dfs(i); 107 108 for (int i = 1; i <= N; i++) 109 inDeg[i] = (sccId[i] == i ? 0 : -1); 110 111 for (int i = 1; i <= N; i++) 112 for (int e = head[i]; e != -1; e = elist[e].next) 113 { 114 int to = elist[e].to; 115 if (sccId[i] != sccId[to]) 116 inDeg[sccId[to]] += 1; //link between two SCCs 117 } 118 119 int head = (int)(std::find(inDeg + 1, inDeg + N + 1, 0) - inDeg); 120 return std::count(inDeg + head + 1, inDeg + N + 1, 0) > 0 ? 0 : sccSize[head]; 121 } 122 123 int main() 124 { 125 input(); 126 printf("%d", solve()); 127 return 0; 128 }

POJ 2186 - Popular Cows - 強連通分量,縮點