1. 程式人生 > >【學習筆記】連通分量與Tarjian

【學習筆記】連通分量與Tarjian

空格 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