高棕櫚傳遞鏈
題目連結 在一張圖中找割點(定義:拔掉它整張圖就不連通了) 作法:如果用暴力,可以每一個點拔掉,做一次DFS判斷是否聯通(效率太差)
效率更高的就是Trajan 演算法 找AP(articulation point)
Tarjan’s algorithm 找 AP 邊的種類可以分成:Tree edge,Back edge,Forward edge,Cross edge,其中無向圖中只會有樹邊跟回邊(按照無向邊DFS的結果,Foward edge 都會變成子孫的back edge, cross edge 會變成樹邊)
維護一個low函數,代表不經過父節點能到的最小時間戳記(進入)的節點,lv函數為當前節點的時間戳記。一個點是不是割點,只要他的任意子節點的low函數大於等於自己的時間戳記編號,那把這個點拔掉,他小孩就走不到祖先了(如果走得到祖先,對於這一棵子樹,拔掉當前節點就可利用此邊繼續連通),所以他就是割點。 更新:$low[now] = min(low[now],low[next])$分別為利用子孫或靠自己
必須要注意的是,割點判斷時要把root的特例獨立判斷。其實root反而比較簡單,如果root有超過一個子樹,代表拔掉root以後會分裂成以每個子樹為單位的連通塊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <bits/stdc++.h> #define ios ios::sync_with_stdio(0),cin.tie(0); #define int long long #define N 1000001 using namespace std;int n,m,low[N],lv[N],es=1 ,root,son_cnt = 0 ;bool visit[N],ans[N];vector<int > edge[N]; void DFS (int now,int father) { visit[now] = 1 ; low[now] = lv[now] = es++; int len = edge[now].size (); for (int i=0 ;i<len;i++){ int next = edge[now][i]; if (now==root && !visit[next])son_cnt++; if (!visit[next]){ DFS (next, now); if (low[next]>=lv[now] && now!=root)ans[now]=1 ; } if (next!=father)low[now] = min (low[now],low[next]); } } signed main () { ios; cin>>n>>m; memset (lv, 0 , sizeof (lv)); memset (low, 0 , sizeof (low)); memset (visit, 0 , sizeof (visit)); memset (ans,0 ,sizeof (ans)); for (int i=0 ;i<m;i++){ int x,y;cin>>x>>y; edge[x].push_back (y); edge[y].push_back (x); } for (int i=0 ;i<n;i++){ if (!visit[i]){ root = i; son_cnt = 0 ; DFS (i, i); if (son_cnt>1 )ans[root] = 1 ; } } for (int i=0 ;i<n;i++)if (ans[i])cout<<i<<endl; }