图的联通性相关
强连通分量
对于有向图
。如果对于任意 。 和 均可以互相到达。则称这个有向图是强连通。 对于一张有向图。其最大的强连通子图被称为强联通分量。简称 SCC。
算法讲解
强连通分量一般使用 tarjan 算法求解。
对于图上连通性的问题,如果直接在图上做会比较困难,我们可以将其转换为 DFS 树上的问题。
DFS 树
如果我们从一个联通(弱联通)图上的某个点出发,最后必然会访问完所有点,并且任意相邻的点只会通过一条边访问。访问的点和边构成了这个图的 DFS 树。
有向图 DFS 树上有四种边,可以自行在 oi-wiki 上查看。
tarjan 算法
我们另
显然
现在的问题是如何保证最大性,即强连通子图最大。我们选取一个基准,即一个强连通分量中一个在 DFS 树内时间戳最小的点的时间戳,为了方便,记节点
对于一个点
-
在 DFS 树中,
是 的祖先。 -
可以到达 。 -
不存在
, 是 的祖先, 能到达 ,
为了方便,我们定义节点
显然,如果求出了
代码实现
显然,根据算法思想,可以如下求
初始定义
因为时间戳的先后性,满足
我们维护一个栈,一旦遇到满足
如果当前遍历到
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。如果一个点有入度,只需满足指向他的点有软件就行。可以如果一个点没有入度,你就必须下载一个,
假设有
-
:先将 个 出度点向 个 入度点连线,在将剩下 个 出度点向第 个 入度点连线。连了 条边。 -
:先将 个 出度点向 个 入度点连线,在将第 个 出度点向剩下 个 入度点连线。连了 条边。
综上,答案为
例1.3:【模板】缩点
由于缩点后是 DAG。可以跑最长路求解
点击查看代码
#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 树上仅仅存在两种边,分别是树边和非树边。
割边定义:对于无向图
。如果删掉某一条边后图的连通分量增加,则称该边为割边。
显然,非树边不会是割边,因为任意非树边连接的两点
定义
换句话说,如果
如果边
前提是
割边还有一个重要的易错点,就是 DFS 树中子节点的
边双连通分量
如果一个无向连通图不具备割边,则称该图为边双连通图。一张无向图的最大联通边双连通图成为该图的边双联通分量,简称 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!