【图论复习】Tarjan 算法(Tarjan LCA 除外)

好久没写 Tarjan,反正也快 CSP 了,赶紧复习一下。
Tarjan 就是基于 dfs 树中的 dfs 序 以及 low 数组来进行搜索,注意不同的算法 low 的更新时不一样的,其他的感觉没什么好讲的,基本上可以说是背代码的吧。
复杂度都是 \(\Theta(n+m)\)

强连通分量

对于一个有向图 \(G=\{V,E\}\),存在点集一个的子集,使得这个子集中任意两个点都可以互相到达,那么这个子集被称为这张图的连联通分量。联通分量的极大值被称为强联通分量。

Tips:
注意需要另外开一个 vis 数组代表这个节点是否在栈内,如果在栈内才能更新 low。

void dfs(int x){
	dfn[x]=low[x]=++cnt; vis[x]=1; st[++top]=x; int i;
	for(i=head[x];i;i=nex[i]){
		if(!dfn[to[i]]) dfs(to[i]);
		if(vis[to[i]]) low[x]=mmin(low[x],low[to[i]]);
	}
	if(low[x]==dfn[x]){
		num++; scc[x]=num; sum[num]=c[x]; vis[x]=0;
		while(st[top]!=x) scc[st[top]]=num,vis[st[top]]=0,sum[num]+=c[st[top]],top--; top--;
	} return;
}

割点

对于一个无向图,如果一个点删除之后,这张图其他点的连通性改变,那么这个点被称之为割点。

Tips:
如果接下来的节点之前访问过,那么只能用下一个节点的 dfn 更新当前点的 low 而非是当前下一个点的 low。
注意判断是不是割点是 low[to[i]]>=dfn[x] (带等号),而且需要特判根。

void dfs(int x,int fa){
	dfn[x]=low[x]=++cnt; int i,d=0;
	for(i=head[x];i;i=nex[i]) if(!dfn[to[i]]){
		d++; dfs(to[i],fa); low[x]=mmin(low[x],low[to[i]]);
		if(low[to[i]]>=dfn[x]&&x!=fa) flag[x]=1;
	} else low[x]=mmin(low[x],dfn[to[i]]);
	if(x==fa&&d>1) flag[x]=1; return;
}

割边

对于一个无向图,如果一条边删除之后,这张图其他点的连通性改变,那么这个边被称之为割边,又称作桥。

Tips:
更新 low 和割点一样。
注意判断是不是割边是 low[to[i]]>dfn[x] (不带等号)。flag[x] 代表 x 和 fa[x] 之间的边是不是割边。
注意重边。

void dfs(int x,int pre){
	dfn[x]=low[x]=++cnt; int i,d=0;
	for(i=head[x];i;i=nex[i]) if(!dfn[to[i]]){
		d++; fa[to[i]]=x; dfs(to[i],i); low[x]=mmin(low[x],low[to[i]]);
		if(low[to[i]]>dfn[x]) flag[x]=1;
	} else if(i!=(pre^1)) low[x]=mmin(low[x],dfn[to[i]]);
	return;
}

点双

对于一个无向图,如果一个极大子图没有一个点是割点,那么这个子图被称为点双连通分量。

Tips:
更新 low 和割点一样。
加入点双条件和割点一样(带等于)。
注意退栈的时候是退到 to[i](要退),当前节点不需要退栈但是需要加入点双(所以存在一些点在多个点双内)

void dfs(int x,int fa){
	dfn[x]=low[x]=++cnt; st[++top]=x; int i,d=0;
	for(i=head[x];i;i=nex[i]) if(!dfn[to[i]]){
		d++; dfs(to[i],x); low[x]=mmin(low[x],low[to[i]]);
		if(low[to[i]]>=dfn[x]){
			num++; ans[num].push_back(x);
			while(st[top+1]!=to[i]) ans[num].push_back(st[top]),top--;
		}
	} else if(to[i]!=fa) low[x]=mmin(low[x],dfn[to[i]]);
	if(fa==0&&d==0) ans[++num].push_back(x); return;
}

边双

对于一个无向图,如果一个极大子图没有一个边是割边,那么这个子图被称为边双连通分量。

Tips:
注意更新 low 和割点一样。
注意加入边双的条件和强连通分量一样。

void dfs(int x,int pre){
	dfn[x]=low[x]=++cnt; st[++top]=x; int i,d=0;
	for(i=head[x];i;i=nex[i]) if(!dfn[to[i]]){
		d++; dfs(to[i],i); low[x]=mmin(low[x],low[to[i]]);
	} else if(i!=(pre^1)) low[x]=mmin(low[x],dfn[to[i]]);
	if(dfn[x]==low[x]){
		++num;
		while(st[top]!=x) ans[num].push_back(st[top]),top--;
		ans[num].push_back(st[top]),top--;
	} return;
}
posted @ 2022-10-17 19:45  jiangtaizhe001  阅读(60)  评论(1编辑  收藏  举报