算法提高课-图论-有向图的强连通分量-AcWing 1174. 受欢迎的牛:tarjan算法求强连通分量、tarjan算法板子、强连通图
文章目錄
- 題目解答
- 題目來源
題目解答
來源:acwing
分析:
強連通圖:給定一張有向圖。若對于圖中任意兩個結點x,y,既存在從x到y的路徑,也存在從y到x的路徑,則稱該有向圖是“強連通圖”。
強連通分量(SCC)指的是強連通圖的極大連通子圖(點最多的)。
說人話,強連通分量中任意兩點都存在邊,再加入任何別的點,它都不再是連通分量。
或者說,對于一張有向圖,強連通分量就是任意兩點都有路徑(x能到y, y也能到x),并且包含點最多的圖。
有向圖的強連通分量有什么用呢?
主要是通過縮點(將強連通分量縮成一個點),把有向圖,轉換為有向無環圖(拓撲圖,DAG)。
如下圖,左圖圈內的是一個強連通分量,通過縮點,轉化為右圖。這種做法其實有很多應用,比如求最短路等。
需要知道的幾個概念:
x是y的父節點,x到y的邊稱為樹枝邊
x是y的祖先節點,x到y的邊稱為前向邊。
樹枝邊是特殊的前向邊。
x是y的祖先節點,y到x的邊稱為后向邊。
在對有向圖進行dfs遍歷時,x是已經搜過的圖的分支(不是前向邊),現在在搜的點是y,y到x的有向邊是橫叉邊。
tarjin算法求強連通分量
引入時間戳的概念:在dfs遍歷的過程中,按照每個結點第一次被訪問的時間順序,依次給予圖中N個結點1~N的整數標記,該標記被稱為時間戳,記為dfn[x].
對每個點定義兩個時間戳:
dfn[u]表示遍歷到u的時間戳;
low[u] 表示從u開始走(遍歷它的子樹),所能遍歷到的最小時間戳是什么。
我們在求強連通分量的時候,求的是每個強連通分量最上面的那個點。
u是所在的強連通分量的最高點,等價于dfn[u]== low[u],因為low[u]表示的是從u開始能夠遍歷到的最小的時間戳,正好等于自己的時間戳,說明什么? 說明u就是這個連通分量的最高點啊!
tarjan算法求強連通分量的模板
背模板的思路
/* 1. 加時間戳; 2. 放入棧中,做好標記; 3. 遍歷鄰點1)如果沒遍歷過,tarjan一遍,用low[j]更新最小值low2) 如果在棧中,用dfn[j]更新最小值low 4.找到最高點1)scc個數++2)do-while循環:從棧中取出每個元素;標志為出棧;對元素做好屬于哪個scc;該scc中點的數量++ */具體模板代碼
// tarjan 算法求強連通分量 // 時間復雜度O(n+ m) void tarjan(int u){// 初始化自己的時間戳dfn[u] = low[u] = ++ timestamp;//將該點放入棧中stk[++ top] = u, in_stk[u] = true;// 遍歷和u連通的點for(int i = h[u]; ~i; i = ne[i]){int j = e[i];if(!dfn[j]){tarjan(j);// 更新u所能遍歷到的時間戳的最小值low[u] = min(low[u], low[j]);}// 如果當前點在棧中//注意棧中存的可能是樹中幾個不同分支的點,因為有橫叉邊存在// 棧中存的所有點,是還沒搜完的點,同時都不是強連通分量的最高點// 這里表示當前強連通分量還沒有遍歷完,即棧中有值else if(in_stk[j])//更新一下u點所能到的最小的時間戳//此時j要么是u的祖先,要么是橫叉邊的點,時間戳小于ulow[u] = min(low[u], dfn[j]);}// 找到該連通分量的最高點if(dfn[u] == low[u]){int y;++ scc_cnt; // 強連通分量的個數++do{// 取出來該連通分量的所有點y = stk[top --];in_stk[y] = false;id[y] = scc_cnt; // 標記點屬于哪個連通分量size_scc[scc_cnt] ++;} while(y != u);} }對于本題的解析:
題意是找到被其他所有牛都歡迎的牛的數量。在有向圖的角度,就是所有的點都可以走到當前這個點。
如果暴力做的話,對于每個點都要dfs或者bfs,看是否所有點都可以到達該點,做一遍的時間復雜度是O(n + m),那么n個點,時間復雜度是O(n(n+m)),這里的n是1w,m是5
w,時間限制是1s,會超時。
優化的解法:
如果是拓撲圖(有向無環圖)的話,就好解決了。為什么呢?只需要統計出度為0的點的數量。如果出度為0的點的數量大于等于2,那么一定不存在最受歡迎的牛(其他所有點都有邊連到該點)。
這是因為是有向無環圖,如果有2個或以上的點出度為0,那么其中1個葉子結點必然有到不了的點,也就是不存在最受歡迎的牛。這個需要畫圖理解一下。
如果只存在一個出度為0的點呢?
綜上,如果是拓撲圖的話,這道題就好做了。實際上,我們可以用強連通分量算法將圖轉換為拓撲圖!
先求出該圖的強連通分量,然后縮點,變成有向無環圖。找到出度為0的點的數量為1的情況,然后統計該點所表示的強連通分量,其中包含多少個點,這里的所有點都可以被其他所有點走到。
ac代碼
#include<bits/stdc++.h> using namespace std;const int N = 10010, M = 50010;int n, m; int h[N], w[M], e[M], ne[M], idx; // 鄰接表一套 // dfn[u] 存的是遍歷到u的時間戳 // low[u]存的是從u出發,遍歷子樹,所能遍歷到的最小的時間戳 //timestamp 就是時間戳 int dfn[N], low[N], timestamp; int stk[N], top; // 棧,和棧頂元素索引 bool in_stk[N]; // 是否在棧中 //id[u]表示u的強連通分量的編號,scc_cnt表示強連通分量的編號 // size_scc[u]表示編號為u強連通分量中點的數量 int id[N], scc_cnt, size_scc[N]; int dout[N];// 記錄新圖中每個點(也就是原圖每個連通分量)的出度void add(int a, int b){e[idx] = b, ne[idx] = h[a], h[a] = idx ++; }void tarjan(int u){//當前點的時間戳dfn[u] = low[u] = ++ timestamp;// 加入棧中stk[++ top] = u, in_stk[u] = true;//遍歷u點的所有鄰點for(int i = h[u]; ~i; i = ne[i]){int j = e[i];if(!dfn[j]){//如果沒有遍歷過tarjan(j); // 遍歷它low[u] = min(low[u], low[j]);}// 當前點在棧當中else if(in_stk[j]) low[u] = min(low[u], dfn[j]);}if(dfn[u] == low[u]){++ scc_cnt; // 更新強連通分量的編號int y;do{y = stk[ top--]; //不斷取出棧內元素in_stk[y] = false;id[y] = scc_cnt; //y元素所屬的連通塊編號size_scc[scc_cnt] ++; //該連通塊內包含的點數}while(y != u); // 直到y不等于u}}int main(){cin >> n >> m;memset(h, -1, sizeof h);while(m --){int a, b;cin >> a >> b;add(a, b);}for(int i = 1; i <= n; i ++){if(!dfn[i])tarjan(i);}// 建有向無環圖// 統計在新圖中所有點的出度for(int i = 1; i <= n; i ++){for(int j = h[i]; ~j; j = ne[j]){int k = e[j];int a = id[i]; //a表示i所在連通分量的編號int b = id[k]; // b表示k所在連通分量的編號//如果點i和點k不在同一個連通塊// dout存的是出度,因為本題只需要出度//在其他題目中,可能是要建邊,因為這里是構造有向無環圖if(a != b) dout[a] ++; // 從a走到b,a的出度++}}// 和本題有關的部分:// zeros是統計在新圖中,出度為0的點的個數// sum表示滿足條件的點(最受歡迎的奶牛)的個數int zeros = 0, sum = 0;for(int i = 1; i <= scc_cnt; i ++){if(!dout[i]){zeros ++;sum += size_scc[i];if(zeros > 1){sum = 0;break;}}}cout << sum << endl; }題目來源
https://www.acwing.com/problem/content/1176/
總結
以上是生活随笔為你收集整理的算法提高课-图论-有向图的强连通分量-AcWing 1174. 受欢迎的牛:tarjan算法求强连通分量、tarjan算法板子、强连通图的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSP认证201509-3模板生成系统[
- 下一篇: CSP认证201509-4高速公路[C+