图的联通性相关

强连通分量

对于有向图 G(V,E)。如果对于任意 {u,v}Vuv 均可以互相到达。则称这个有向图是强连通。

对于一张有向图。其最大的强连通子图被称为强联通分量。简称 SCC。

算法讲解

强连通分量一般使用 tarjan 算法求解。

对于图上连通性的问题,如果直接在图上做会比较困难,我们可以将其转换为 DFS 树上的问题。

DFS 树

如果我们从一个联通(弱联通)图上的某个点出发,最后必然会访问完所有点,并且任意相邻的点只会通过一条边访问。访问的点和边构成了这个图的 DFS 树。

有向图 DFS 树上有四种边,可以自行在 oi-wiki 上查看。

tarjan 算法

我们另 S(x) 表示 DFS 树中 x 子树的部分。

显然 x 可以到达任意 yS(x),且 xy 的路径上所有的点都可以被 x 到达,都可以到达 y。此时,如果存在一条返租边 (y,x)。则 xy 的路径上所有的点及其边构成的子图强连通。

现在的问题是如何保证最大性,即强连通子图最大。我们选取一个基准,即一个强连通分量中一个在 DFS 树内时间戳最小的点的时间戳,为了方便,记节点 x 的时间戳为 dfnx

对于一个点 y。如果 xy 所在强连通分量内时间戳最小的点。必然满足以下条件

  • 在 DFS 树中,xy 的祖先。

  • y 可以到达 x

  • 不存在 zzy 的祖先,y 能到达 zdfnz>dfnx

为了方便,我们定义节点 y最小追溯值dfnx,写为 lowy=dfnx,特殊的,存在 lowu=dfnu,节点 u 就是上文所说的 x

显然,如果求出了 low 数组,所有 low 值相等的节点构成一个强连通分量。

代码实现

显然,根据算法思想,可以如下求 low 值。

lowu=min(u,v)Elowv

初始定义

lowu=dfnu

因为时间戳的先后性,满足 lowx=dfnx 中的 x 所在强连通分量的节点一定在 S(x) 中,不过 S(x) 中的节点不一定在强连通分量内,其可能独属于一个其他强连通分量。

我们维护一个栈,一旦遇到满足 dfnu=lowu。就把栈中节点不断弹出直到 u 被弹出。这样在求 dfnx=lowx 时,属于其他强连通分量的节点都弹了出去,就可以保证答案的正确性。

如果当前遍历到 x。显然 x 的祖先都在栈中,可以同时维护 x 的祖先。

void tarjan(int u){
	dfn[u]=low[u]=++tot;
	stk[++top]=u,mk[u]=1;
	for(int i=ver[u];i;i=nxt[i]){
		int v=to[i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}else if(mk[v])low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]){
		scc++;
		while(stk[top+1]!=u){
			mk[stk[top]]=0;
			f[stk[top]]=scc;
			top--;
		}
	}
	return;
}

注意到这里用到了 low[u]=min(low[u],dfn[v]) 。巧妙的借助了代码的特性,这并不影响算法的正确性。

SCC 缩点

「缩点」。就是把一个强连通分量看成一个点,连接两个联通分量之间的有向边看成一条边,应为强连通分量的极大性,可以得到结论

两个强连通分量不可能存在于一个环中。

换句话说

缩点后的新图一定是 DAG。

来点例题?

例1.1:[USACO03FALL / HAOI2006] 受欢迎的牛 G

如果我们对原图缩点,一个强连通分量内所有的牛都是互相崇拜的。能当明星的牛要么是一头,要么是一个强连通分量内。否则无解。

统计度数,存在出度的点中都不可能成为明星,如果存在多个点没有出度,则无解,因为必须满足所有人都“爱慕”

例1.2:[USACO5.3] 校园网Network of Schools

缩点后一定会形成若干个 DAG。如果一个点有入度,只需满足指向他的点有软件就行。可以如果一个点没有入度,你就必须下载一个,0 入度点数就是第一问答案。

假设有 p0 入度点,q0 出度点,第二问要求我们最少加几条边使原图缩成一个 SCC 。我们先把所有点入度和出度变为非零零。

  • pq:先将 q0 出度点向 q0 入度点连线,在将剩下 pq0 出度点向第 q0 入度点连线。连了 p 条边。

  • p<q:先将 p0 出度点向 p0 入度点连线,在将第 p0 出度点向剩下 pq0 入度点连线。连了 q 条边。

综上,答案为 max(p,q)

例1.3:【模板】缩点

由于缩点后是 DAG。可以跑最长路求解

dpv=max(u,v)Edpu+wv

wv 表示 v 所在强连通分量内点的权值和。

点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int N=1e4+10,M=1e5+10;
int ver[N],nxt[M],to[M],idx,n,m,w[N],u[M],v[M],ww[N],ans=-1;
int dfn[N],low[N],stk[N],mk[N],from[N],top,d[N],dp[N],scc,tot;
queue<int>q;
void add(int x,int y){
    to[++idx]=y,nxt[idx]=ver[x],ver[x]=idx;
}
void tarjan(int u){
    dfn[u]=low[u]=++tot;
    stk[++top]=u,mk[u]=1;
    for(int i=ver[u];i;i=nxt[i]){
        int tp=to[i];
        if(!dfn[tp]){
            tarjan(tp);
            low[u]=min(low[u],low[tp]);
        }else if(mk[tp])low[u]=min(low[u],dfn[tp]);
    }
    if(low[u]==dfn[u]){
        scc++;int v,cnt=0;
        while(stk[top+1]!=u){
            v=stk[top--];
            mk[v]=0;
            from[v]=scc;
            cnt+=w[v];
        }
        ww[scc]=cnt;
    }
    return;
}
void topsort(){
    for(int i=1;i<=scc;i++){
        if(!d[i])q.push(i);
        dp[i]=ww[i];
    }
    while(q.size()){
        int t=q.front();q.pop();
        for(int i=ver[t];i;i=nxt[i]){
            int tp=to[i];
            d[tp]--;
            dp[tp]=max(dp[tp],dp[t]+ww[tp]);
            if(!d[tp])q.push(tp);
        }
    }
    return;
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",w+i);
    for(int i=1;i<=m;i++){
        scanf("%d %d",u+i,v+i);
        add(u[i],v[i]);
    }
    for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
    idx=0;
    memset(ver,0,sizeof(ver));
    memset(nxt,0,sizeof(nxt));
    memset(to,0,sizeof(to));
    for(int i=1;i<=m;i++){
        if(from[u[i]]!=from[v[i]])add(from[u[i]],from[v[i]]),d[from[v[i]]]++;
    }
    topsort();
    for(int i=1;i<=scc;i++){
        ans=max(ans,dp[i]);
    }
    printf("%d\n",ans);
    return 0;
}

双连通分量

割边

无向图的 DFS 树上仅仅存在两种边,分别是树边和非树边。

割边定义:对于无向图 G(V,E)。如果删掉某一条边后图的连通分量增加,则称该边为割边。

显然,非树边不会是割边,因为任意非树边连接的两点 u,v 之间必然被若干树边连接。

定义 Son(x)x 在 DFS 树中子节点点集。因为时间戳的定义和祖后代的特性。如果 ySon(x),且从 y 出发无法到达 Son(y) 以外的部分,删去 (x,y)y 将“失联”。则 (x,y) 为割边。

换句话说,如果 y 所能到达的时间戳最小节点为 x。则记 lowy=dfnx。显然,lowu 的求解方法如下。

lowu=minvSon(u)lowv

如果边 (x,y) 是割边,显然

lowy<dfnx

前提是 ySon(x)

割边还有一个重要的易错点,就是 DFS 树中子节点的 low 不能从父节点获取,不过有的题目中会出现重边,一个很好的判断方法是记录边的编号,编号为 i 的边和编号为 i1 的边其实是一条边,注意在建图时边的编号从 2 开始标记。

边双连通分量

如果一个无向连通图不具备割边,则称该图为边双连通图。一张无向图的最大联通边双连通图成为该图的边双联通分量,简称 e-DCC。

求法很简单,求出割边后标记,DFS 划分联通分量即可。

void tarjan(int u,int edge){
    dfn[u]=low[u]=++tot;
    for(int i=ver[u];i;i=nxt[i]){
        int v=to[i];
        if(!dfn[v]){
            tarjan(v,i);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<low[v])bridge[i]=bridge[i^1]=1;
        }else if(i!=(edge^1))low[u]=min(low[u],dfn[v]);
    }
    return;
}
void dfs(int x){
    v[dcc].push_back(x),mk[x]=1;
    for(int i=ver[x];i;i=nxt[i]){
        if(mk[to[i]]||bridge[i])continue;
        dfs(to[i]);
    }
    return;
}
posted @   zuoqingyuan111  阅读(101)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示