Tarjan

求强联通分量

解释见代码

#include <bits/stdc++.h>
using namespace std;
const int N = 100005; // 根据题目最大点数修改
int n, m;
vector<int> G[N];
int dfn[N]/*时间戳,代表u的访问顺序*/, low[N]/*代表u能回到的最早祖先*/, scc_id[N]; // scc_id[u] 表示 u 属于哪个 SCC
int dfs_clock = 0, scc_cnt = 0;
stack<int> st;
int in_st[N];
// Tarjan 主过程
void tarjan(int u) 
{
	dfn[u] = low[u] = ++dfs_clock;
	st.push(u);
	in_st[u] = 1;
	for (int v : G[u]) {
		if (!dfn[v]) {  // v 未访问
			tarjan(v);
			low[u] = min(low[u], low[v]);
		} else if (in_st[v]) {  // v 在栈中,说明是返祖边
			low[u] = min(low[u], dfn[v]);
		}
	}
	
	// 如果 u 是 SCC 的根
	if (dfn[u] == low[u]) {
		++scc_cnt;
		while(1){
			int x = st.top(); st.pop();
			in_st[x] = 0;
			scc_id[x] = scc_cnt;
			if (x == u) break;
		}
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int u, v;
		cin >> u >> v;
		G[u].push_back(v); // 有向边 u -> v
	}
	for (int i = 1; i <= n; i++) {
		if (!dfn[i]) tarjan(i);
	}
	cout << "SCC 总数: " << scc_cnt << "\n";
	for (int i = 1; i <= n; i++) {
		cout << "点 " << i << " 属于 SCC #" << scc_id[i] << "\n";
	}
	return 0;
}

桥和割点

桥的话要在tarjan(v,u)之后判断\(low_v>dfn_u\),原理是如果\(v\)可以返回的最小节点的时间戳大于\(u\)节点的时间戳,代表\(u\)\(v\)之间只有一条边互相连接。

至于割点,则判断\(low_v>=dfn_u\)代表如果\(v\)能回到的最早祖先是\(u\),那么代表删除\(u\)节点后\(v\)无法回到\(u\)之前的节点,所以\(u\)即是一个割点,割点代码如下:

#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 5;
int n, m, R;
int dn, dfn[N], low[N], cnt, buc[N]; // dfn 是时间戳 d, low 是 g
vector<int> e[N];
void dfs(int id) {
  dfn[id] = low[id] = ++dn; // 将 low[id] 初始化为 dn 不会导致错误, 且一般都这么写
  int son = 0;
  for(int it : e[id]) {
    if(!dfn[it]) {
      son++, dfs(it), low[id] = min(low[id], low[it]);
      if(low[it] >= dfn[id] && id != R) cnt += !buc[id], buc[id] = 1;
    }
    else low[id] = min(low[id], dfn[it]);
  }
  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);
  }
  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;
}

P3388 【模板】割点(割顶)

求边双连通分量

边双连通分量即一个无向图中,去掉一条边后仍互相连通的极大子图。(单独的一个点也可能是一个边双连通分量)
换言之,一个边双连通分量中不包含桥

#include <bits/stdc++.h>
using namespace std;
const int N=5005;
int ti,f,r,dfn[N],low[N],color[N],du[N],cid=0;
set<pair<int,int>> bridge;
vector<int> G[N];
void tarjan(int u,int fa)//用来找桥
{
	dfn[u]=low[u]=++ti;
	for(int v:G[u])
	{
		if(!dfn[v])
		{
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u])
				bridge.insert({min(u,v),max(u,v)});
		}
		else if(fa!=v)
		{
			low[u]=min(dfn[v],low[u]);
		}
	}
}
void dfs(int u)//用来标记缩点
{
	color[u]=cid;
	for(int v:G[u])
	{
		if(bridge.count({min(u,v),max(u,v)})||color[v]!=0)
			continue;
		dfs(v);
	}
}
int main()
{
	cin>>f>>r;
	for(int u,v,i=1;i<=r;i++)
	{
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	tarjan(1,0);
	for(int i=1;i<=f;i++)
	{
		if(!color[i])
		{
			cid++;
			dfs(i);
		}
	}
}
posted @ 2025-08-27 16:33  wtnbl  阅读(6)  评论(0)    收藏  举报