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;
}
求边双连通分量
边双连通分量即一个无向图中,去掉一条边后仍互相连通的极大子图。(单独的一个点也可能是一个边双连通分量)
换言之,一个边双连通分量中不包含桥。
#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);
}
}
}

浙公网安备 33010602011771号