1. 程式人生 > >Tarjan&割點&割邊&點雙&邊雙&縮點

Tarjan&割點&割邊&點雙&邊雙&縮點

一個點 函數 連通塊 一個 強聯通 枚舉 pat noi2012 部分

文末有福利。

Tarjan是通過搜索樹和壓棧完成的,維護兩個東西:dfn[i](時間戳)、low[i](通過搜索樹外的邊i(返祖邊),節點能到達的最小節點的時間戳)。

跑完Tarjan,縮點,可以得到DAG圖(有向無環圖),可以再建圖或統計入度出度。

在有向圖中,可以找強連通分量SCC(極大強聯通子圖)(任意兩點可以互達):

多維護一個vis【i】表示在不在棧中。

 1 void tarjan_(int u)
 2 {
 3     stack[++tp]=u;
 4     dfn[u]=low[u]=++num;
 5     vis[u]=1;
 6     for
(int i=head[u];i;i=ed[i].nxt) 7 { 8 int v=ed[i].to; 9 if(!dfn[v]) 10 { 11 tarjan_(v); 12 low[u]=min(low[u],low[v]); 13 } 14 else if(vis[v]) 15 { 16 low[u]=min(low[u],dfn[v]); 17 } 18 }
19 if(dfn[u]==low[u]) 20 { 21 int temp; 22 scc_num++; 23 do 24 { 25 temp=stack[tp--]; 26 vis[temp]=0; 27 siz[scc_num]++; 28 co[temp]=scc_num; 29 }while(temp!=u); 30 } 31 }

無向圖中,可以找割點(在一個無向連通圖中,如果有一個頂點集合,刪除這個頂點集合以及這個集合中所有頂點相關聯的邊以後,原圖變成多個連通塊,就稱這個點集為割點集合。)、割邊(也叫橋)(圖G的邊e是割邊,當且僅當e不在G的任何一個圈上)。又可由此得出點雙連通分量(v-DCC)(一個無向圖中不存在割點)和邊雙連通分量(e-DCC)(若一個無向圖中不存在割邊(橋))。

簡單說就是:

割點:去掉這個點,原本連通的圖變得不連通。

割邊:去掉這條邊,原本連通的圖變得不連通。

點雙:不存在割點的子圖。

邊雙:不存在割邊的子圖。

粘代碼:

點雙:

attention:

  1. 用vector存,不能像其他一樣用co【i】來染色,因為因為一個割點可能在多個點雙中。
  2. 割點也要放在每一個與之相連的點雙中。
  3. 縮點後新圖中,是割點與點雙間隔排列地連接的。每一個點雙中有幾個割點就說明新圖中該節點有幾條連邊。
  4. 【重中之重】判斷割點的 if(low[y]>=dfn[x]) 在枚舉每條邊的循環中,因為一個割點可能在多個點雙中。
 1 int root,cnt;
 2 bool cut[maxn];//cut【i】==1表示節點i是割點
 3 void tarjan(int x)
 4 {
 5     dfn[x]=low[x]=++cnt;
 6     stack[++tp]=x;
 7     int flag=0;
 8     for(int i=head[x];i;i=ed[i].nxt)
 9     {
10         int y=ed[i].to;
11         if(!dfn[y])
12         {
13             tarjan(y);
14             low[x]=min(low[x],low[y]);
15             if(low[y]>=dfn[x])
16             {
17                 flag++;
18                 if(root!=x||flag>1) cut[x]=true;//搜索樹的跟需要特殊處理,必須有兩個子樹才是割點
19                 dcc_num++;
20                 int temp;
21                 do{
22                     temp=stack[tp--];
23                     dcc[dcc_num].push_back(temp);
24                 }while(temp!=y);
25                 dcc[dcc_num].push_back(x);
26             }
27         }
28         else low[x]=min(low[x],dfn[y]);
29     }
30 }

主函數中:
for(int i=1;i<=n;i++) 
{ if(!dfn[i]) root=i,tarjan(i); }

邊雙:

attention:

  1. 由於是無向圖,故與SCC不同,需要記錄來時的點或邊。
  2. 由於可能有重邊的情況,記錄來時的點就不行啦,就要像我一樣記錄來時的邊(鏈式前向星存圖時從2開始存,故每一對邊的序號關系為a^1==b,b^1==a(亦或))。
  3. 與點雙不同,判斷要放在循環之外。
  4. 除此之外,還有另一種寫法:求出割邊後dfs。
 1 bool bri[maxm<<1]; 
 2 int dfn[maxn],low[maxn],stack[maxn],tp,dcc_num;
 3 int cnt,co[maxn];
 4 void tarjan(int x,int pre_ed)
 5 {
 6     dfn[x]=low[x]=++cnt;
 7     stack[++tp]=x;
 8     for(int i=head[x];i;i=ed[i].nxt)
 9     {
10         if(i==(pre_ed^1)) continue;
11         int y=ed[i].to;
12         if(!dfn[y])
13         {
14             tarjan(y,i);
15             low[x]=min(low[x],low[y]);
16         }
17         else low[x]=min(low[x],dfn[y]);
18     }
19     if(low[x]==dfn[x])
20     {
21         dcc_num++;
22         int temp;
23         do{
24             temp=stack[tp--];
25             co[temp]=dcc_num;
26         }while(temp!=x);
27     }
28 }

福利來了:

例題(模板題)

SCC:P2863 [USACO06JAN]牛的舞會The Cow Prom P2341 [HAOI2006]受歡迎的牛 P1726 上白澤慧音 P2746 [USACO5.3]校園網Network of Schools

割點:P3388 【模板】割點(割頂)

點雙:P3225 [HNOI2012]礦場搭建 P2783 有機化學之神偶爾會做作弊 P3469 [POI2008]BLO-Blockade

邊雙:P2860 [USACO06JAN]冗余路徑Redundant Paths

部分內容借鑒GMK大佬課件,表示感謝。

Tarjan&割點&割邊&點雙&邊雙&縮點