Tarjan学习笔记
Tarjan 算法,研究图的连通性。(以下按照算阶上的顺序整理)
无向图
割点
对于给定无向连通图,删除其中一个节点以及所有与其关联的边之后,图不再连通,则称此节点为无向图的割点。
割边(桥)
对于给定无向连通图,删除其中一条边,图不再连通,则称此边为无向图的割边(桥)。
追溯值
用 low 数组储存, low[x] 为搜索树中以 x 为根的子树中的节点和通过一条不在搜索树上的边可以到达以 x 为根的子树的节点的点的时间戳的最小值。
Tarjan求割边
割点判定法则:对于 x 在搜索树上的一个子节点 y,存在 dfn[x]<low[y],那么 (x,y) 为图的一条割边。
void tarjan(int x,int las)
{
dfn[x]=low[x]=++now;
for(int i=lk[x];i;i=nxt[i])
{
int y=to[i];
if(!dfn[y])
{
tarjan(y,i^1);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x])//
{
ans[++z]={min(x,y),max(x,y)};//存割边
}
}
else if(i!=las)low[x]=min(low[x],dfn[y]);
}
}
边双连通分量
先跑一遍 Tarjan 标记所有割边,再在不经过割边的情况下 dfs,每个连通块是一个边双连通分量。
Tarjan 求割点
割点判定法则: 对于结点 \(x\),若其不是搜索树的根节点,则 \(x\) 为搜索树的个点当且仅当存在 \(x\) 的一个子结点 \(y\),满足 \(dfn_x\le low_y\)。
代码和上面只有一点点不同。
void tarjan(int x)
{
dfn[x]=low[x]=++now;int flag=0;//计数
for(int i=lk[x];i;i=nxt[i])
{
int y=to[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])//满足条件
{
++flag;//
if(x!=root||flag>1)//
{
if(!cut[x])++z;//
cut[x]=true;//
}
}
}
else low[x]=min(low[x],dfn[y]);//与 x 相连的点的 dfs 序都可以用来更新 low x
}
点双连通分量
点双联通分量的缩点并不是非常直观,需要从每个割点发出多条边连接它所属的每一个点双代表的点。所以用圆方树。原图上每个点为圆点,每个点双连通分量新建一个方点,点双中所有的点与此方点连边,得到一棵树,这棵树叫做圆方树。它有非常多美好的性质,比如任意一条路径都是圆点方点交错的,非叶的圆点都是割点等等。
有向图
Tarjan 算法在有向图上最多的应用是求强连通分量。
强连通分量
一张图中所有的点都可以两两互达,称这张图为强联通的。有向图的极大强联通子图称为强连通分量。
判定:
void tarjan(int x)
{
dfn[x]=low[x]=++now;
st[++tp]=x;ins[x]=true;
for(int i=lk[x];i;i=nxt[i])
{
int y=to[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y])low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
++cnt;int y;
do{
y=st[tp--];ins[y]=false;
c[y]=cnt;ac[c[y]]+=a[y];
}while(x!=y);
}
}
浙公网安备 33010602011771号