连通性问题

强连通分量

介绍:在有向图 $ G $ 中,任意两个顶点 $ u $ 和 $ v $,存在两条路径 $ u \to v $ 和 $ v \to u$,则这个图为一个强连通图,一个有向图的极大(最大的)强连通子图称为强连通分量。

Kosaraju算法

两遍dfs,第一次求每个点访问完的顺序\(d[i]\)、第二次以第一次dfs访问从晚到早的顺序对反向图进行dfs,删除遍历的节点,每次删除的节点构成一个强连通分量。时间复杂度 $ O(n+m) $。

void dfs(int x){
	vis[x]=1;
	for(int i=1;i<=n;i++)
		if(a[x][i] && !vis[i]) dfs(i); 
	d[++cnt]=x;
}
void dfs(int x){
	vis[x]=cnt;
	for(int i=1;i<=n;i++)
		if(a[i][x] && !vis[i]) dfs(i); 
}
void kos(){
	for(int i=1;i<=n;i++)
		if(!vis[i]) dfs(i);
	memset(vis,0,sizeof(vis));
	cnt=0;
	for(int i=n;i>=1;i--)
		if(!vis[d[i]]) {++cnt;dfs2(d[i]);}
}

Tarjan 算法

统一的,对于所有的 $ tarjan $ 算法,都以一个 $ u $ 为根建一颗搜索树,对于遍历到的节点,维护两个值 $ dfn $ 和 $ low $。其中 $ dfn $ 为时间戳,$ low $ 是以这个点为根的搜索树可以访问到最小的 $ dfn $。

对于 $ low $ 其实包含两种,第一种是他的子孙的 $ low $,第二种是他的子孙可以访问到的点的 $ dfn $。如果说一个点的 $ dfn $ 等于他的 $ low $,也就是说这个点 $ u $ 可以通向子孙 $ v $。并且他的子孙 $ v $ 也存在一条路径到 $ u $。

时间复杂度 $ O(n+m) $。

void tarjan(int u){
	dfn[u]=low[u]=++cnt;
	vis[u]=1;stk[++top]=u;//vis=0 未访问 =1 在此搜索树内 =2在其他的强连通分量中
	int len=g[u].size();
	for(int i=0;i<len;i++) {
		int v=g[u][i];
		if(vis[v]==2) continue;
		if(!vis[v]) 
			tarjan(v);
		if(vis[v]==1) low[u]=min(low[u],dfn[v]);
		else low[u]=min(low[u],low[v]);
	}
	if(dfn[u]==low[u]){
		++cnt2;int v;
		do{
			v=stk[top--];
			belong[v]=cnt2;
			ans[cnt2].push_back(v);
			vis[v]=2;
		}
		while(u^v) ;
	}
	
}

点双

割点

当一个点删掉后,图的联通块数量改变,这个点为割点。

利用 $ tarjan $ 求解。当 $ low_v \ge dfn_u$ 时,说明 $ v $ 能到达的点只能是 $ u $ 和其子孙而时间戳小于 $ u $ 的点则无法访问,删去 $ u $ 后,时间戳小于 $ u $ 的点就无法访问了,故此点为割点(不为根)。

搜索树的根为特例,如果此点为根,且树边(连向他的子孙)大于一,否则反之。

#include<bits/stdc++.h>

using namespace std;
const int maxn=2e4+5;
int n,m;
int head[maxn],tot;
struct edge{
	int nt,to;
}g[maxn*10];
void add(int x,int y){
	g[++tot].to=y;
	g[tot].nt=head[x];
	head[x]=tot;
}
int dfn[maxn],low[maxn];
int vis[maxn],cnt,stk[maxn],top,ans;
int p,l[maxn];
bool cut[maxn];
void tarjan(int u,int rt){
	dfn[u]=low[u]=++cnt;vis[u]=1;
	int son=0;
	for(int i=head[u];i;i=g[i].nt){
		int v=g[i].to;
		if(vis[v]==0) {
			son++;
			tarjan(v,rt);
			if(u!=rt && low[v]>=dfn[u]) cut[u]=1;
			low[u]=min(low[u],low[v]);
		}
		else low[u]=min(low[u],dfn[v]);
	}
	if(u==rt && son>=2) {
		cut[u]=1;
	}
}
vector<int>q[maxn];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i,i);
	for(int i=1;i<=n;i++)
		if(cut[i]) ans++;
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)
		if(cut[i]) printf("%d ",i);
	return 0;
}

点双联通分量

点双联通:没有割点的图。

割点有几个性质,割点属于多个点双联通分量,连接几个点双连通分量。非割点则只属于一个点双联通分量(多个就是割点了)。

和求割点相似,但一个割点属于多个点双联通分量,出栈时注意判不弹出割点。

#include<bits/stdc++.h>
using namespace std;
int n,m,low[500005],dfn[500005],ans,cnt;
int nxt[4000005],head[500005],go[4000005],k;
vector<int> dcc[500005];
stack<int>sta;
void add(int u,int v)
{
	nxt[++k]=head[u];
	head[u]=k;
	go[k]=v;
}
void tarjan(int x,int root)//求割点的改版(其实不需要root)
{
	dfn[x]=low[x]=++cnt;
	if(x==root&&!head[x])//孤立点判定
	{
		dcc[++ans].push_back(x);
	}
	sta.push(x);
	for(int i=head[x];i;i=nxt[i])
	{
		int g=go[i];
		if(!dfn[g])
		{
			tarjan(g,root);
			low[x]=min(low[x],low[g]);
			if(low[g]>=dfn[x])
			{
				ans++;
				int p;
				do{//弹栈
					p=sta.top();
					sta.pop();
					dcc[ans].push_back(p);
				}while(p!=g);//注意此处,因为要求是不到达出点
				dcc[ans].push_back(x);//别忘了加入源点!
			}
		}
		else
		low[x]=min(low[x],dfn[g]);
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		if(x==y) continue;//重边
		add(x,y);
		add(y,x);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i]) tarjan(i,i);//注意图可能不连通
	}
	cout<<ans<<endl;
	for(int i=1;i<=ans;i++)
	{
		cout<<dcc[i].size()<<" ";
		for(int j=0;j<dcc[i].size();j++)
		cout<<dcc[i][j]<<" ";
		cout<<endl;
	}
}

边双

割边

如果联通图中一条边被删去后图不连通,那么这条边为割边(桥)。

tarjan求解。当 $ low_v > dfn_u $,从 $ v $ 没有第二条路径可以走到 $ u $ 了,所以$ u \to v $ 这条边为割边。

code

int dfn[maxn],low[maxn],cnt;
bool bj[maxn];//是否为桥
void tarjian(int now,int in) {
	dfn[now]=low[now]=++cnt;
	for(int i=head[now];i;i=nex[i]) {
		int st=to[i];
		if(!dfn[st]) {
			tarjian(st,i);
			low[now]=min(low[now],low[st]);
			if(low[st]>dfn[now]) bj[i]=true,bj[i^1]=true;
		}
		else if(i!=(in^1)) low[now]=min(low[now],dfn[st]);
	}
}

边双联通分量

与求桥差别不大,一个边双联通图,其所有边都至少存在于一个环中,而桥联通多个联通分量,删掉桥以后,每个联通分量都是边双联通分量,$ dfs $搞即可。

posted @ 2023-08-14 20:50  point_fish  阅读(16)  评论(1)    收藏  举报