tarjan算法在无向图中概念较多,包括割点、桥、点双连通分量、边双连通分量等。
割点
割点判定法则:对于一个节点u,当且仅当u存在一个子节点j,使得dfn[u]
≤
\leq
≤low[j]。特别地,如果u是搜索树中的根节点,那么当且仅当u存在两个子节点j1,j2,使得dfn[u]
≤
\leq
≤min(low[j1],low[j2])。
求割点的模板:
#include<iostream>
using namespace std;
const int N=2e4+5,M=2e5+5;
int head[N],to[M],ne[M],idx;
void add(int x,int y){
ne[++idx]=head[x];
to[idx]=y;
head[x]=idx;
}
int dfn[N],low[N],tmp;
int root,cut[N];
void tarjan(int u){
dfn[u]=low[u]=++tmp;
int flag=0;
for(int i=head[u];i;i=ne[i]){
int j=to[i];
if(!dfn[j]){
tarjan(j);
low[u]=min(low[u],low[j]);
if(dfn[u]<=low[j]){
flag++;
if(u!=root || flag>1) cut[u]=true;
}
}
else low[u]=min(low[u],dfn[j]);
//因为判定法则可以取等号,所以不用考虑父节点以及重边的情况
}
}
int main(){
int n,m;
cin>>n>>m;
while(m--){
int x,y;
cin>>x>>y;
add(x,y);add(y,x);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
root=i;
tarjan(i);
}
}
int ans=0;
for(int i=1;i<=n;i++){
if(cut[i]) ans++;
}
cout<<ans<<endl;
for(int i=1;i<=n;i++){
if(cut[i]) cout<<i<<' ';
}
return 0;
}
例题1:AcWing 363.B城
本题需要考虑删去点i对于图的连通性的影响:若点i不是割点,那么答案是2(n-1);若点i是割点,那么图将被划分为若干个连通块。由割点的性质容易知道:连通块为点i、点i的子树、点i的上层节点。那么每个连通块中的任意一个点,都会和连通块外的任意一点构成一对不连通点对。所以只需要在计算割点的同时统计答案即可,代码如下:
#include<iostream>
using namespace std;
const int N=1e5+5,M=1e6+5;
typedef long long ll;
int n,m;
int head[N],to[M],ne[M],idx;
void add(int x,int y){
ne[++idx]=head[x];
to[idx]=y;
head[x]=idx;
}
bool cut[N];
int dfn[N],low[N],tmp;
ll siz[N],ans[N];
void tarjan(int u){
low[u]=dfn[u]=++tmp;siz[u]=1ll;
ll sum=0ll;
for(int i=head[u];i;i=ne[i]){
int j=to[i];
if(!dfn[j]){
tarjan(j);
siz[u]+=siz[j];
low[u]=min(low[u],low[j]);
if(low[j]>=dfn[u]){//割点判定法则
cut[u]=true;
ans[u]+=(n-siz[j])*siz[j];
sum+=siz[j];
}
}
else low[u]=min(low[u],dfn[j]);
}
if(cut[u]){
ans[u]+=(n-1-sum)*(sum+1)+n-1;
}
else ans[u]=2*(n-1);
}
int main(){
cin>>n>>m;
while(m--){
int x,y;
cin>>x>>y;
add(x,y);add(y,x);
}
tarjan(1);
for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
return 0;
}
桥、边双连通分量
桥(割边)判定法则:dfn[u]<low[j]
桥的判定法则较为麻烦,需要考虑父节点的情况,而且若出现重边,那么该两点间一定不存在桥。
桥的性质:一定是搜索树上的一条边
代码如下:
在这里插入代码片