强联通分量+割点+割边+双联通分量(tarjan)

一.强联通分量及缩点

模板题

1.定义

给定一张有向图,若存在一个点集,该点集内的点可通过路径任意到达其他所有点,则称该点集为一个强联通分量。(一个点也可为一个强联通分量)

2.算法tarjan

结果:将强联通分量缩成一个点,让一个有环图变成一张无环图
实现方式:记low[u]为u点所能到达的最小次序点,记dfn[u]为u点的次序,stk存储经过的点,在dfs的过程中不断的维护。回溯时,若点u的dfn==low,即点u不会到达u上层的点了,则从u开始跑经过的点均在u所在的强联通分量里,就将其染色

#include<bits/stdc++.h>
using namespace std;
const int N=20005;
const int M=1e5+5;
struct ppp {
    
    
	int u,v,next;
} e[2*M];
int vex[N],k;
int dfn[N],low[N],cnt;
int stk[N],top,color[N],co,ans[N],instk[N];
int n,m;
//dfn为次序,low为该点所能遍历的次序最早的点 
void add(int u,int v) {
    
    
	k++;
	e[k].u=u;
	e[k].v=v;
	e[k].next=vex[u];
	vex[u]=k;
	return;
}
void tarjan(int u) {
    
    
	dfn[u]=low[u]=++cnt;
	instk[u]=1;
	stk[++top]=u;//用栈储存强连通,u点入栈 
	for(int i=vex[u]; i; i=e[i].next) {
    
    
		int v=e[i].v;
		if(!dfn[v]) {
    
    
			tarjan(v,root);
			low[u]=min(low[v],low[u]);
			//如果v能遍历到的点比u能遍历到的最小点小,就改值 
		}
		else if(instk[v])low[u]=min(low[u],dfn[v]);
		//如果v的次序比u的遍历到的最小点小,就改值 
	}
	if(dfn[u]==low[u]){
    
    //则该点为强连通的最早点 
		co++;
		while(1){
    
    
			int t=stk[top];//从栈顶到这个点的所有点,都是该强联通分量的点集 
			color[t]=co;//属于一个强联通分量的点颜色相同 
			top--;
			instk[t]=0;//出栈 
			if(t==u)break;
		}
	} 
}
void solve(){
    
    
	co=n; //如果需要对缩完后的点建图,则co要从n+1开始
	for(int i=1; i<=n; i++) {
    
    
       if(!dfn[i])tarjan(i,i);
	}
	for(int u=1;u<=n;u++){
    
    
		for(int i=vex[u];i;i=e[i].next){
    
    
			int v=e[i].v;
			if(color[u]!=color[v])add(color[u],color[v]);
			//后来是以缩点后的强联通分量颜色来建无向图
		}
	}
}
int main() {
    
    
	int u,v;
	cin>>n>>m;
	for(int i=1; i<=m; i++) {
    
    
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	solve(); 
	return 0;
}  

为什么是在instk[v]的时候low[u]=min(low[u],dfn[v])?
当非instk的时候,v点可能是已经结束了他的强联通分量
这时若将low[u]修改为low[v],就可能导致u点无法构成与他本该构成的强联通分量

3.开始刷题

例题1:消息扩散

只需要在tarjan完之后,求入度为0的点的数量即可

void xxks(){
    
    
	int ans=0;
	for(int u=1;u<=n;u++){
    
    
		for(int i=vex[u];i;i=e[i].next){
    
    
			int v=e[i].v;
			if(color[u]!=color[v])in[color[v]]++;
			//求入度为0的点为扩散源 
		}
	}
	for(int i=1;i<=co;i++){
    
    
		if(in[i]==0)ans++;
	}
	cout<<ans;
} 

二.割点与割边

割点模板题

1.定义

割点:给定一张无向连通图,若存在点u满足删除该点后,连通图不再连通,则该点u为该图的割点
割边:给定一张无向连通图,若存在边e满足删除该边后,连通图不再连通,则该边e为该图的割边(也称“桥”)

2.算法tarjan

过程

tarjan跑一个深搜优先树
对于根节点:若其有两个以上的孩子,则该点为割点
对于非根点:若从u出发跑,不能到达u的上层,则u点为割点
对于任意点:若从边uv跑,不能跑到u点以及u点的上层,则该边uv为割边

根据定义很好写出代码

void tarjan(int u,int root) {
    
    
	dfn[u]=low[u]=++cnt;
	int child=0;
	for(int i=vex[u]; i; i=e[i].next) {
    
    
		int v=e[i].v;
		if(!dfn[v]) {
    
    
			tarjan(v,root);
			low[u]=min(low[v],low[u]);
			if(low[v]>=dfn[u]&&u!=root)cutnode[u]=1;
            //子节点出发能到的最早点在该节点后或等于该节点则该点为割点
			if(low[v]>dfn[u])cutedge[i]=1;
			//子节点出发能到的最早点在该节点后则该边为割边
			//反向边也为割边,即编号从0开始存的,i与i^1
			child++;//统计根节点的孩子数 
		}
		else low[u]=min(low[u],dfn[v]);
	}
	if(u==root&&child>=2)cut[u]=1;//根节点有两个子树则为割点 
	if(dfn[u]==low[u]){
    
    
		co++;
		while(1){
    
    
			int t=stk[top];
			color[t]=co;
			top--;
			instk[t]=0;
			if(t==u)break;
		}
	} 
}

为什么这次没有像求强联通分量的时候一样else if(instk[])???
因为在无向图求割点割边的时候不会出现存在可到达点属于它自己的强联通分量的情况

三.双联通分量

1.点双联通分量

定义:若无向连通图中,去掉任意一个节点都不会改变此图的连通性,即不存在割点,则称作点双连通图。一个无向连通图中的每一个极大点双连通子图称作此无向图的点双连通分量。
性质:各点双连通分量由割点连接
算法:tarjan求割点,删除割点,dfs求连通块即可

void tarjan(int u,int root) {
    
    
	dfn[u]=low[u]=++cnt;
	int child=0;
	for(int i=vex[u]; i!=-1 i=e[i].next) {
    
    
		int v=e[i].v;
		if(!dfn[v]) {
    
    
			tarjan(v,root);
			low[u]=min(low[v],low[u]);
			if(low[v]>=dfn[u]&&u!=root)cutnode[u]=1; 
			child++;
		}
		else low[u]=min(low[u],dfn[v]);
	}
	if(u==root&&child>=2)cutnode[u]=1;
}
void dfs(int u){
    
    
	vis[u]=1;
	for(int i=vex[u];i!=-1;i=e[i].next){
    
    
		int v=e[i].v;
		if(cutnode[v]||vis[v])continue;
		dfs(v);
	} 
}
void solve() {
    
    
	tarjan(1,1);
	for(int i=1;i<=n;i++)if(!vis[i]&&cutnode[i]==0)dfs(i);
}

2.边双连通分量

定义:若无向连通图中,去掉任意一条边都不会改变此图的连通性,即不存在割边,则称作边双连通图。一个无向连通图中的每一个极大边双连通子图称作此无向图的边双连通分量。
性质:各边双连通分量由割边连接
算法:tarjan求割边,删除割边,dfs求连通块即可

void tarjan(int u,int root) {
    
    
	dfn[u]=low[u]=++cnt;
	for(int i=vex[u]; i!=-1 i=e[i].next) {
    
    
		int v=e[i].v;
		if(!dfn[v]) {
    
    
			tarjan(v,root);
			low[u]=min(low[v],low[u]);
			if(low[v]>dfn[u])cutedge[i]=cutedge[i^1]=1; 
		}
		else low[u]=min(low[u],dfn[v]);
	}
}
void dfs(int u){
    
    
	visnode[u]=1;
	for(int i=vex[u];i!=-1;i=e[i].next){
    
    
		int v=e[i].v;
		if(cutedge[i]||visdege[i])continue;
		visedge[i]=visedge[i^1]=1;
		dfs(v);
	} 
}
void solve() {
    
    
	tarjan(1,1);
	for(int i=1;i<=n;i++)if(!visnode[i])dfs(i);
}

猜你喜欢

转载自blog.csdn.net/weixin_43602607/article/details/110183297