强连通分量

Kosaraju\large\color{skyblue} \mathcal Kosaraju

算法步骤:

  1. 建图与反图。
  2. 遍历图,记录回溯的顺序,使用栈记录回溯顺序。
  3. 按照栈顺序,遍历反图。

Code\large\color{skyblue} Code

#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;
}

Tarjan\large\color{skyblue} \mathcal Tarjan

使用 dfni,lowidfn_i,low_i,分别表示点 ii 的遍历序,从点 ii 能达到最早遍历的点。

将遍历的点加入栈中,统计强连通中的点。更新 dfn,lowdfn,low。 (以下强连通分量表示极大强连通分量)

到回溯时,说明 ii 点可以到达的点已经记录完毕。

对于一个强连通分量,必然有一个点是 dfni=lowidfn_i=low_i 的。证明:若强连通分量中 dfnilowidfn_i\ne low_i,那么最小的 dfni>lowidfn_i>low_i,还能到达别的点,就会有更小的 dfnjdfn_j,矛盾。

因此,我们在 dfni=lowidfn_i=low_i 时,即强连通分量中的点已经统计完,将在栈的点中吐出来,直到吐到点 ii

Code\large\color{skyblue} Code

#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;
}

似乎 Tarjan\color{skyblue}Tarjan 的常数更小一点。


缩点\large\color{skyblue}\textbf{缩点}

将有向图转化为有向无环图(DAG\operatorname{DAG})。有向无环图有很多美好的性质:

  • 使用拓扑排序,类似于 dp,可求最短路等。
  • 在特殊情况时(每个强连通分量只有一个入度)看作一棵树,做树上 dp。
  • \dots\dots

将环去除,即将一个强连通分量看作点。

枚举所有点,枚举与之相连的边(即所有的边)。此处需要注意:只有边到达的点不属于当前点的强连通分量,才能连接。否则会出现自环。

这样我们就得到了缩点之后的图。有些时候,还需要注意重边。


Code\large\color{skyblue}Code

Tarjan\color{skyblue}Tarjan

#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;
}

Kosaraju\color{skyblue}Kosaraju

#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;
}

practice\large\color{skyblue}practice

石中练习

posted @ 2023-10-05 15:25  cjrqwq  阅读(4)  评论(0编辑  收藏  举报  来源