强连通分量
算法步骤:
- 建图与反图。
- 遍历图,记录回溯的顺序,使用栈记录回溯顺序。
- 按照栈顺序,遍历反图。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
int n,m,nm,scc[N],s[N],c;
vector<int>e[N],re[N];
bool v[N];
void dfs(int from){
v[from]=1;
for(int to:e[from])if(!v[to])dfs(to);
s[c++]=from;
}
void rfs(int from){
scc[from]=nm;
for(int to:re[from])if(!scc[to])rfs(to);
}
int ko(){
for(int i=1;i<=n;i++)if(!v[i])dfs(i);
for(int i=n-1;~i;i--)
if(!scc[s[i]]) nm++, rfs(s[i]);
return nm;
}
int main() {
cin>>n>>m;
for(int i=1,x,y;i<=m;i++) {
cin>>x>>y;
e[x].push_back(y), re[y].push_back(x);
}
cout<<ko();
return 0;
}
使用 ,分别表示点 的遍历序,从点 能达到最早遍历的点。
将遍历的点加入栈中,统计强连通中的点。更新 。 (以下强连通分量表示极大强连通分量)
到回溯时,说明 点可以到达的点已经记录完毕。
对于一个强连通分量,必然有一个点是 的。证明:若强连通分量中 ,那么最小的 ,还能到达别的点,就会有更小的 ,矛盾。
因此,我们在 时,即强连通分量中的点已经统计完,将在栈的点中吐出来,直到吐到点 。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
int n,m,nm,scc[N],snm,dfn[N],low[N],s[N],num[N],top;
vector<int>e[N];
void dfs(int from) {
dfn[from]=low[from]=++nm;
s[++top]=from;
for(int to:e[from])
if(!dfn[to]) dfs(to), low[from]=min(low[from],low[to]);
else if(!scc[to]) low[from]=min(low[from],dfn[to]);
if(dfn[from]==low[from]) {
snm++;
while(scc[s[top]]=snm,s[top--]^from);
}
}
int main() {
cin>>n>>m;
for(int i=1,x,y;i<=m;i++) {
cin>>x>>y;
e[x].push_back(y);
}
for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i);
cout<<snm;
return 0;
}
似乎 的常数更小一点。
将有向图转化为有向无环图()。有向无环图有很多美好的性质:
- 使用拓扑排序,类似于 dp,可求最短路等。
- 在特殊情况时(每个强连通分量只有一个入度)看作一棵树,做树上 dp。
将环去除,即将一个强连通分量看作点。
枚举所有点,枚举与之相连的边(即所有的边)。此处需要注意:只有边到达的点不属于当前点的强连通分量,才能连接。否则会出现自环。
这样我们就得到了缩点之后的图。有些时候,还需要注意重边。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int n,m,nm,scc[N],s[N],top,rd[N],cd[N];
//rd,cd 分别为入度,出度
vector<int>e[N],ne[N];
//ne 为新图
queue<int>q;
bool v[N];
int dfn[N],low[N],tag;
void dfs(int from) {
dfn[from]=low[from]=++tag;
s[++top]=from;
for(int to:e[from])
if(!dfn[to]) dfs(to), low[from]=min(low[from],low[to]);
else if(!scc[to]) low[from]=min(low[from],dfn[to]);
if(dfn[from]==low[from]) {
nm++;
while(scc[s[top]]=nm,s[top--]^from);
}
}
int main() {
cin>>n>>m;
for(int i=1,x,y;i<=m;i++) {
cin>>x>>y;
e[x].push_back(y),re[y].push_back(x);
}
for(int i=1;i<=n;i++) {
if(!dfn[i]) dfs(i);
}
for(int i=1;i<=n;i++) {
for(auto j:e[i]) {
if(scc[i]!=scc[j])
ne[scc[i]].push_back(scc[j]), rd[scc[j]]++, cd[scc[i]]++;
}
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int n,m,nm,scc[N],s[N],c,rd[N],cd[N];
//rd,cd 分别为入度,出度
vector<int>re[N],e[N],ne[N];
//ne 为新图
queue<int>q;
bool v[N];
void dfs(int from){
v[from]=1;
for(auto to:e[from])if(!v[to])dfs(to);
s[c++]=from;
}
void rfs(int from) {
scc[from]=nm;
for(int to:re[from])if(!scc[to])rfs(to);
}
void ko(){
for(int i=1;i<=n;i++)if(!v[i])dfs(i);
for(int i=n-1;~i;i--)
if(!scc[s[i]]) nm++, rfs(s[i]);
}
int main() {
cin>>n>>m;
for(int i=1,x,y;i<=m;i++) {
cin>>x>>y;
e[x].push_back(y),re[y].push_back(x);
}
ko();
for(int i=1;i<=n;i++) {
for(auto j:e[i]) {
if(scc[i]!=scc[j])
ne[scc[i]].push_back(scc[j]), rd[scc[j]]++, cd[scc[i]]++;
}
}
return 0;
}
见石中练习。