首页 > 其他分享 >图的联通性相关

图的联通性相关

时间:2024-08-19 11:37:16浏览次数:6  
标签:连通 联通 int DFS dfn low 相关 分量

强连通分量

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

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

算法讲解

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

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

DFS 树

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

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

tarjan 算法

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

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

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

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

  • 在 DFS 树中,\(x\) 是 \(y\) 的祖先。

  • \(y\) 可以到达 \(x\)。

  • 不存在 \(z\),\(z\) 是 \(y\) 的祖先,\(y\) 能到达 \(z\),\(dfn_z>dfn_x\)

为了方便,我们定义节点 \(y\) 的最小追溯值为 \(dfn_x\),写为 \(low_y=dfn_x\),特殊的,存在 \(low_u=dfn_u\),节点 \(u\) 就是上文所说的 \(x\)。

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

代码实现

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

\[low_u=\min\limits_{(u,v)\in E}low_v \]

初始定义

\[low_u=dfn_u \]

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

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

如果当前遍历到 \(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\) 入度点数就是第一问答案。

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

  • \(p\le q\):先将 \(q\) 个 \(0\) 出度点向 \(q\) 个 \(0\) 入度点连线,在将剩下 \(p-q\) 个 \(0\) 出度点向第 \(q\) 个 \(0\) 入度点连线。连了 \(p\) 条边。

  • \(p<q\):先将 \(p\) 个 \(0\) 出度点向 \(p\) 个 \(0\) 入度点连线,在将第 \(p\) 个 \(0\) 出度点向剩下 \(p-q\) 个 \(0\) 入度点连线。连了 \(q\) 条边。

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

例1.3:【模板】缩点

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

\[dp_v=\max\limits_{(u,v)\in E}dp_u+w_v \]

\(w_v\) 表示 \(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 树中子节点点集。因为时间戳的定义和祖后代的特性。如果 \(y\in Son(x)\),且从 \(y\) 出发无法到达 \(Son(y)\) 以外的部分,删去 \((x,y)\) 后 \(y\) 将“失联”。则 \((x,y)\) 为割边。

换句话说,如果 \(y\) 所能到达的时间戳最小节点为 \(x\)。则记 \(low_y=dfn_x\)。显然,\(low_u\) 的求解方法如下。

\[low_u=\min\limits_{v\in Son(u)}low_v \]

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

\[low_y<dfn_x \]

前提是 \(y\in Son(x)\)。

割边还有一个重要的易错点,就是 DFS 树中子节点的 \(low\) 不能从父节点获取,不过有的题目中会出现重边,一个很好的判断方法是记录边的编号,编号为 \(i\) 的边和编号为 \(i\oplus 1\) 的边其实是一条边,注意在建图时边的编号从 \(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;
}

标签:连通,联通,int,DFS,dfn,low,相关,分量
From: https://www.cnblogs.com/zuoqingyuan/p/18367019

相关文章

  • HttpClient、IHttpClientFactory、HttpClientHandler 和 HttpMessageHandler 的生命周
    在C#中,HttpClient、IHttpClientFactory、HttpClientHandler和HttpMessageHandler的生命周期密切相关,它们共同影响着网络请求的性能、资源管理和可靠性。以下是它们的生命周期分析:1.HttpClient的生命周期默认行为:HttpClient是线程安全的,设计为可以在应用程序的整个生命......
  • vue3 - 详细实现内网使用离线百度地图功能,在vue3中无需网络离线使用百度地图相关功能,
    效果图在vue3、nuxt3项目开发中,完成内网离线使用百度地图详细教程,让vue3网站无需网络就能加载百度地图及相关功能,完整的百度地图离线使用及地图瓦片的下载教程、更新教程等,vue3百度地图内网离线使用显示地图及各种功能,无论js/ts语法都可以使用,详解百度地图离线加载机制及整......
  • StringBuilder类相关操作
     //StringBuilder的定义及相关操作intint1=100;StringBuilderstr1=newStringBuilder("哈哈哈,",100);str1.Append("你变了");//Append函数Console.WriteLine(str1);str1.Appe......
  • 不可变字符串string的相关操作
    staticvoidMain(string[]args){//截取字符串stringstr1="ABCDEFGHIJKLMN";stringstr2=str1.Substring(0,4);//从0位开始截取,共截取4位;Console.WriteLine(str2);Console.WriteLin......
  • 树莓派开发相关知识
    1、ubuntumate烧写开发环境:我们选择ubuntu-mate操作系统。准备工作:sd卡及读卡器及sd卡、计算机下载ubuntu-mate镜像文件ubuntu-mate-16.04.2-desktop-armhf-raspberry-pi。使用Win32DiskImager工具,将ubuntu-mate安装至sd卡中。打开ubuntu-mate-16.04.2-desktop-armhf-ras......
  • FreeRTOS 任务相关API函数
    FreeRTOS任务相关API函数函数描述uxTaskPriorityGet()获取任务优先级vTaskPrioritySet()设置任务优先级uxTaskGetNumberOfTasks()获取系统中任务的数量uxTaskGetSystemState()获取所有任务状态信息vTaskGetInfo()获取指定单个的任务信息xTaskG......
  • Java集合相关面试题(超详细)附:Java详细面试资料直链
    Java集合相关面试题内容搬运资料,来源见文章末尾仅为分享,不涉及任何获利行为,~(~ ̄▽ ̄)~可别发律师函啊链接:面试资料提取码:s4w8ArrayList底层实现是数组LinkedList底层实现是双向链表HashMap的底层实现使用了众多数据结构,包含了数组、链表、散列表、红黑树等1算法复杂度分......
  • 今日份主要内容:目录类,文件流相关类
    今日份主要内容:目录类,文件流相关类目录类:Directory类,DirectoryInfo类文件的输入与输出类:FileStream类,MemoryStream类,StreamReader类,StreamWriter类,StringReader类,StringWriter类目录类窗体的设计窗体设置privatestringselectedPath=string.Empty;//想调用其他......
  • 北京联通 IPTV转单播操作记录
    前提:北京联通的IPTV设备连接光猫的LAN2口。光猫的LAN2口为ACCESS口,接入终端不需要支持VLAN也不需要VLAN号。首次开通需要接机顶盒认证,通过后不验证终端MAC。光猫的LAN2口已开DHCP,无需手动分配地址,看到获取的是192.168.1的网段。光猫的LAN2口没有ipv6,怀疑是运维没给开。可以......
  • linux C语言remove函数及相关函数
    讯飞星火LinuxC语言中的remove函数用于删除指定的文件或目录。其原型如下:intremove(constchar*pathname);参数:pathname:要删除的文件或目录的路径。返回值:成功时返回0,失败时返回-1。相关函数:rename:用于重命名文件或目录。intrename(constchar*old_path,......