【學習筆記】連通分量與Tarjian
阿新 • • 發佈:2018-09-03
空格 top set dfs memset ridge ins define 同學
連通分量與Tarjian
所以Tarjian到底怎麽讀
強連通分量
- 基本概念
- 強連通 如果兩個頂點可以相互通達,則稱兩個頂點強連通
- 強連通圖 如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。
- 強連通 如果兩個頂點可以相互通達,則稱兩個頂點強連通
- Tarjian
Tarjan算法是基於對圖深度優先搜索的算法,每個強連通分量為搜索樹中的一棵子樹。搜索時,把當前搜索樹中未處理的節點加入一個堆棧,回溯時可以判斷棧頂到棧中的節點是否為一個強連通分量。 - 定義
- dfn[u]: 為節點u搜索的次序編號(時間戳)
- low[u]: 為u或u的子樹能夠追溯到的最早的棧中節點的次序號。
- 判定
- low[u]:=min(low[u],dfn[v])——(u,v)為後向邊,v不是u的子樹;
- low[u]:=min(low[u],low[v])——(u,v)為樹枝邊,v為u的子樹;
- 當DFN(u)=Low(u)時,以u為根的搜索子樹上所有節點是一個強連通分量。
模板題:信息傳遞
題目描述
有n個同學(編號為1到n)正在玩一個信息傳遞的遊戲。在遊戲裏每人都有一個固定的信息傳遞對象,其中,編號為i的同學的信息傳遞對象是編號為Ti同學。
遊戲開始時,每人都只知道自己的生日。之後每一輪中,所有人會同時將自己當前所知的生日信息告訴各自的信息傳遞對象(註意:可能有人可以從若幹人那裏獲取信息,但是每人只會把信息告訴一個人,即自己的信息傳遞對象)。當有人從別人口中得知自己的生日時,遊戲結束。請問該遊戲一共可以進行幾輪?
輸入
輸入共2行。
第1行包含1個正整數n表示n個人。 n ≤ 200000
第2行包含n個用空格隔開的正整數T1,T2,……,Tn,其中第i個整數Ti示編號為i的同學的信息傳遞對象是編號為Ti的同學,Ti≤n且Ti≠i。數據保證遊戲一定會結束。
輸出
輸出共 1 行,包含 1 個整數,表示遊戲一共可以進行多少輪。
樣例輸入
5
2 4 2 3 1
樣例輸出
3
提示
遊戲的流程如圖所示。當進行完第 3 輪遊戲後,4 號玩家會聽到 2 號玩家告訴他自
己的生日,所以答案為 3。當然,第 3 輪遊戲後,2 號玩家、3 號玩家都能從自己的消息
來源得知自己的生日,同樣符合遊戲結束的條件。
#include<bits/stdc++.h> #define ll long long #define ull unsigned long long #define met(a,x) memset(a,x,sizefo(a)) #define inf 0x3f3f3f3f using namespace std; const int maxn=2e5+10; int n,tol,dep; int head[maxn],low[maxn],dfn[maxn],ans=maxn; bool vis[maxn]; stack<int>st; struct edge { int v,next; }e[maxn]; void addedge(int u,int v) { e[++tol].next=head[u]; e[tol].v=v; head[u]=tol; } void tarjian(int u) { dfn[u]=low[u]=++dep; st.push(u); vis[u]=true; for(int i=head[u];i;i=e[i].next){ int v=e[i].v; if(!dfn[v]){ tarjian(v); low[u]=min(low[u],low[v]); }else if(vis[v]){ low[u]=min(low[u],dfn[v]); } } if(low[u]==dfn[u]){ int cnt=0; while(1){ int now=st.top(); st.pop(); vis[u]=0; cnt++; if(now==u)break; } if(cnt>1) ans=min(ans,cnt); } } int main() { scanf("%d",&n); int v; for(int i=1;i<=n;i++){ scanf("%d",&v); addedge(i,v); } for(int i=1;i<=n;i++){ if(!dfn[i]){ tarjian(i); } } printf("%d\n",ans); return 0; }
求割點
- 基本概念:
- 割點:若刪掉某點後,原連通圖分裂為多個子圖,則稱該點為割點。
- 若low[v]>=dfn[u],則u為割點。因low[v]>=dfn[u],則說明v通過子孫無法到達u的祖先。那麽對於原圖,去掉u後,必然會分成兩個子圖。
- 模板
/*
* 求無向圖的割點和橋
* 可以找出割點和橋,求刪掉每個點後增加的連通塊。
* 需要註意重邊的處理,可以先用矩陣存,再轉鄰接表,或者進行判重
*/
const int MAXN = 10010;
const int MAXM = 100010;
struct Edge{
int to,next;
bool cut;//是否為橋的標記
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN];
int Index,top;
bool Instack[MAXN];
bool cut[MAXN];
int add_block[MAXN];//刪除一個點後增加的連通塊
int bridge;
void addedge(int u,int v){
edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut = false
;
head[u] = tot++;
}
void Tarjan(int u,int pre){
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
int son = 0;
int pre_cnt = 0; //處理重邊,如果不需要可以去掉
for(int i = head[u];i != ?1;i = edge[i].next){
v = edge[i].to;
if(v == pre && pre_cnt == 0){pre_cnt++;continue;}
if( !DFN[v] ){
son++;
Tarjan(v,u);
if(Low[u] > Low[v])Low[u] = Low[v];
//橋
//一條無向邊(u,v) 是橋,當且僅當(u,v) 為樹枝邊,且滿足
DFS(u)<Low(v)。
if(Low[v] > DFN[u]){
bridge++;
edge[i].cut = true;
edge[i^1].cut = true;
}
//割點
//一個頂點u 是割點,當且僅當滿足(1) 或(2) (1) u 為樹根,且u 有多於一個子樹。
//(2) u 不為樹根,且滿足存在(u,v) 為樹枝邊(或稱父子邊,
//即u 為v 在搜索樹中的父親),使得DFS(u)<=Low(v)
if(u != pre && Low[v] >= DFN[u]){//不是樹根
cut[u] = true;
add_block[u]++;
}
}
else if( Low[u] > DFN[v])
Low[u] = DFN[v];
}
//樹根,分支數大於1
if(u == pre && son > 1)cut[u] = true;
if(u == pre)add_block[u] = son ? 1;
Instack[u] = false;
top??;
}
【學習筆記】連通分量與Tarjian