DSF森林
树边:dfs森林上的边
前向边:祖先->后代的非树边
返祖边:后代->祖先的非树边
横插边:后面的分支连接到前面的分支的边
强连通分量
强连通分量:任意两个点相互可达,也就是存在一个环连接了所有的点
u和v强连通,v和w强连通,所以u,v,w三个是强连通的
Tarjan
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector<int> e[N];
vector< vector<int> > scc;
int dfn[N], low[N], idx, belong[N], ins[N], cnt;
stack<int> s;
int n, m;
void dfs(int u){
dfn[u] = low[u] = ++idx;
s.push(u); ins[u] = 1;
for(auto v : e[u]){
if(!dfn[v]) dfs(v);
if(ins[v]) low[u] = min(low[u], low[v]);
}
if(low[u] == dfn[u]){
vector<int> c;
cnt ++;
while(true){
int v = s.top();
s.pop();
ins[v] = 0;
c.push_back(v);
belong[v] = cnt;
if(u == v) break;
}
sort(c.begin(), c.end());
scc.push_back(c);
}
}
int main(){
scanf("%d %d", &n, &m);
for(int i = 0; i < m; i ++){
int u, v; scanf("%d %d", &u, &v);
e[u].push_back(v);
}
for(int i = 1; i <= n; i ++){
if(!dfn[i]) dfs(i);
}
sort(scc.begin(), scc.end());
for(int i = 0; i < scc.size(); i ++){
for(auto res : scc[i]) printf("%d ", res);
puts("");
}
}
Kosaraju
// 先dfs一遍,得到一个出栈顺序
// 重要:有向无环图出栈顺序是反图的拓扑序
// 一个任意有向图,scc缩成一个点,形成一个拓扑序
// 所以最后一个出栈的一定是源点
// 出栈顺序逆序遍历,每次从未分配点开始遍历反图
// 第一个出栈的不一定是汇点
// 可以用位运算优化
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector<int> e[N], erev[N];
vector< vector<int> > scc;
vector< int > out, c;
int vis[N];
int n, m;
void dfs1(int u){
vis[u] = 1;
for(auto v : e[u]){
if(!vis[v]) dfs1(v);
}
out.push_back(u);
}
void dfs2(int u){
vis[u] = 1;
for(auto v : erev[u]){
if(!vis[v]) dfs2(v);
}
c.push_back(u);
}
int main(){
scanf("%d %d", &n, &m);
for(int i = 0; i < m; i ++){
int u, v; scanf("%d %d", &u, &v);
e[u].push_back(v);
erev[v].push_back(u);
}
for(int i = 1; i <= n; i ++){
if(!vis[i]) dfs1(i);
}
reverse(out.begin(), out.end());
memset(vis, 0, sizeof vis);
for(int i = 0; i < out.size(); i ++){
if(!vis[out[i]]){
c.clear();
dfs2(out[i]);
sort(c.begin(), c.end());
scc.push_back(c);
}
}
sort(scc.begin(), scc.end());
for(int i = 0; i < scc.size(); i ++){
for(auto res : scc[i]) printf("%d ", res);
puts("");
}
}
SCC缩点和DP