Tarjan

xiaoluosibky發表於2024-09-15

P3388 【模板】割點(割頂)

1、注意在遍歷時要儲存根節點編號,判斷時需要特判根節點

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;   
int n,m,r;
int dn,dfn[N],low[N],cnt,buc[N];
vector<int> e[N];
void dfs(int id){
	//標記時間戳 
	dfn[id] = low[id] = ++dn;
	int son = 0;
	//遍歷該點相鄰點 
	for(int it : e[id]){
		//如果沒有被遍歷 
		if(!dfn[it]){
			//其子節點加一 
			son++;
			//遍歷當前節點 
			dfs(it);
			//進行更新 
			low[id] = min(low[id],low[it]);
			//若id的子節點不能夠透過其他路徑走到比id時間戳更小的點 並且 id不為根節點 
			//id就為割點 
			if(low[it] >= dfn[id] && id != r){
				//割點數量加一 
				cnt += !buc[id];
				//將id標記為割點 
				buc[id] = 1;
			}			
		}else{
			//更新id_low為其鄰點的最小時間戳 
			//得到透過id點能夠到達的最小值 
			low[id] = min(low[id],dfn[it]);
		}
	}
	//如果id是根節點,但其有兩個以上的子樹,那麼id為割點 
	if(son >= 2 && id == r){
		//更新,標記 
		cnt += !buc[id];
		buc[id] = 1;
	}
}
int main(){
	cin>>n>>m;
	//鄰接表儲存連點 
	for(int i = 1; i <= m; ++i){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	//遍歷dfs 
	for(int i = 1; i <= n; ++i){
		//沒有被遍歷 
		if(!dfn[i]){
			//初始化頂點 
			r = i;
			dfs(i);
		}
	}
	cout<<cnt<<endl;
	for(int i = 1; i <= n; ++i){
		if(buc[i]){
			cout<<i<<" ";
		}
	}
	return 0;
} 

洛谷 P1656 炸鐵路

讀圖,鄰接表存圖,tarjan求割邊(橋),排序,輸出

注意父親節點的判斷,如果兩點之間有重邊的情況需要注意

如果在第一次之外還回到了父親結點(說明可能有重邊),則按像計算兒子結點的方法一樣用父親結點的值更新當前結點的值。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 10010;
int n, m, x, y, idx, dfn[MAXN], low[MAXN], ans,a;
//dfn儲存固定時間戳, low儲存其出點能夠到達的最小時間戳
vector<int> G[MAXN];//使用鄰接表存圖 
struct Edge{int from, to;} edge[MAXN];//題目要求,a < b 
bool cmp(const Edge a, const Edge b){if(a.from != b.from)return a.from < b.from; return a.to < b.to;}//題目要求排序 
inline void add_edge(int x, int y){edge[ans].from = min(x, y); edge[ans].to = max(x, y); ans++;}//在答案中加入一條邊 
void dfs(int cur, int fa){//cur當前節點, fa 為其父親節點 
	int child;
	dfn[cur] = ++idx;// 計算當前節點的時間戳 
	low[cur] = dfn[cur];//初始化 當前可以訪問到的最早時間戳肯定是自己的時間戳 
	bool vis = false;//防止在有重邊時會出錯
	for(int i = 0; i < G[cur].size(); ++i){//遍歷它的所有出點 
		child = G[cur][i];//取出出點 
		//如果這個節點被訪問過 
		if(dfn[child]){
			if(child == fa && !vis) vis = true;//如果是父親節點 且是第一次訪問不做任何操作 
			else low[cur] = min(low[cur], dfn[child]);//如果訪問到了不是父親節點的節點 更新low值 
			//如果在第一次之外還回到了父親結點,則按像計算兒子結點的方法一樣用父親結點的值更新當前結點的值。
		}
		//如果這個節點之前沒有被訪問過 
		if(!dfn[child]){
			dfs(child, cur);//進行dfs 
			if(dfn[cur] < low[child]) add_edge(cur, child);//如果滿足條件(當前節點向下走無法回到之上的節點),
			//說明當邊是割邊 將其加入答案之中 
			low[cur] = min(low[cur], low[child]);
			//更新當前節點的low值 
		}
	}
}
int main(){
	cin >> n >> m;
	for(int i = 0; i < m; ++i) cin >> x >> y,G[x].push_back(y),G[y].push_back(x);
	for(int i = 1; i <= n; ++i) if(!dfn[i]) dfs(i, i);//圖可能不連通, 初始時將自己的父親節點初始化為自己,不會出錯 
	sort(edge, edge + ans, cmp);//將答案進行排序 
	for(int i = 0; i < ans; ++i) cout << edge[i].from <<" "<< edge[i].to << endl;//輸出 
	return 0;
} 

相關文章