强连通分量

32 篇文章 1 订阅

强连通分量

前置芝士

首先熬过了 树剖 m j l mjl mjl 终于开始讲解一些码量偏小的题目。

于是,就愉快地探索什么是强连通分量……

以上是它的一些基本概念。

其实强连通就是一个,主要是解决一些有向图求最值问题(最少需要几个物品完成所有节点覆盖……)。

Tarjan

之前在学习 LCA 时就以接触过这位大佬的算法,是离线的,时间复杂度达到了优秀的 O ( n + q ) O(n+q) O(n+q)不过常数极大,被树剖秒掉。但是 Tarjan强连通分量 是在线的,也是正常搜索的时间复杂度,于是它就成为了不二之选。


R o b e r t Robert Robert E . E. E. T a r j a n Tarjan Tarjan(罗伯特·塔扬, 1948 1948 1948~),生于美国加州波莫纳,计算机科学家。

T a r j a n Tarjan Tarjan 发明了很多算法和数据结构。不少他发明的算法都以他的名字命名,以至于有时会让人混淆几种不同的算法。比如求 各种连通分量的Tarjan 算法,求 LCA(Lowest Common Ancestor,最近公共祖先)的 Tarjan 算法并查集SplayToptree 也是 T a r j a n Tarjan Tarjan 发明的。


首先送上最诚挚的膜拜。

然后开始对 Tarjan 的讲解。

首先需要用到一个东西叫做追溯值(上图有),并且需要用到一个栈来存储当前已经遍历到的结点(后面有用)。追溯值用来判定是否是强连通分量,其中的 l o w low low 数组调整分为以下两种情况:(不妨假设当前遍历到的结点为 u u u,即将遍历到的结点为 v v v

1.如果 v v v 未被遍历到(即 d f n v = 0 dfn_v=0 dfnv=0,此处 d f n dfn dfn 数组存储的是树的 dfn序),那么取:

l o w u = min ⁡ ( l o w u , l o w v ) low_u=\min(low_u,low_v) lowu=min(lowu,lowv).

2.如果不满足条件1但是并没有被记录属于任何一个强连通分量(即 p e r v = 0 per_v=0 perv=0,此处 p e r per per 数组表示该结点属于强连通分量的标号,未被标记即为初始值 0 0 0),那么取:

l o w u = min ⁡ ( l o w u , D f n v ) low_u=\min(low_u,Dfn_v) lowu=min(lowu,Dfnv).

有了这样的处理,接下来进行强连通分量的标记环节。


如果进过了以上的处理后 l o w u = D f n u low_u=Dfn_u lowu=Dfnu,那么就可以确定 u u u 所在的强连通分量的所有结点都已被遍历到(即都已放入了栈),于是我们再开一个 p e r per per 数组,它的含义上文已解释,一个整数型变量 r e t ret ret 用于存储当前的编号。

于是首先:

per[u]=++cnt;

然后:

while(1){
	int tmp=s.top();s.pop();
	per[tmp]=cnt;
	if(tmp==x)break;
}

其中 s s s 为之前处理出来的栈,知道栈弹出的元素等于当前遍历到的元素,那么整个强连通分量就已遍历结束。

Floyd

上一次接触 Floyd,是在求全源最短路时,它的代码框架如下:

int dis[505][505];
//code.
for(int k=1;k<=n;++k){
    for(int x=1;x<=n;++x){
        for(int y=1;y<=n;++y){
            dis[x][y]=min(dis[x][y],dis[x][k]+dis[k][y]);
		}
	}
}

很显然,它的时间复杂度为 O ( n 3 ) O(n^{3}) O(n3),虽说很大,不过图论的题目一般 n n n 都比较小,所以是完全可过的。

而现在关于它的运用就是判断是否可以相通,如下:

bool dis[505][505];
//code.
for(int k=1;k<=n;++k){
    for(int x=1;x<=n;++x){
        for(int y=1;y<=n;++y){
            dis[x][y]|=dis[x][k]&&dis[k][y];
		}
	}
}

这只是强连通分量的一个基本运用。

出度与入度

首先必须在有向图中,对于一个结点,用多少条边直接到达了它,就为它的入度,反之则为出度。

它们的求取防止为下(首先要在跑过一次 Tarjan 的基础下,不妨设 R R R 数组为入度, C C C 数组为出度):

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int ch=0,num=0;
	char c=getchar();
	while(!isdigit(c))ch|=(c=='-'),c=getchar();
	while(isdigit(c))num=(num<<1)+(num<<3)+(c^48),c=getchar();
	return ch?-num:num;
}
inline void write(int x){
	if(x<0)putchar('-'),x=-x;
	if(x>=10)write(x/10);
	putchar(x%10+'0');
}
int n,Dfn[3005],low[3005],ret,cnt,per[3005],val[3005],R[3005],C[3005];
vector<int>G[3005];
stack<int>s;
inline void dfs(int x){
	Dfn[x]=low[x]=++cnt,s.push(x);
	for(int i=0;i<G[x].size();++i){
		if(!Dfn[G[x][i]]){
			dfs(G[x][i]);
			low[x]=min(low[x],low[G[x][i]]);
		}else if(!per[G[x][i]]){
			low[x]=min(low[x],Dfn[G[x][i]]);
		}
	}
	if(low[x]==Dfn[x]){
		++ret;
		while(1){
			int tmp=s.top();s.pop();
			per[tmp]=ret;
			if(tmp==x)break;
		}
	}
	return;
}
signed main(){
	n=read();
	for(int i=1;i<=n;++i){
		int x=read(),y=read();
		G[x].push_back(y);
	}
	for(int i=1;i<=n;++i){
		if(!Dfn[i]){
			dfs(i);
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=0;j<G[i].size();++j){
			if(per[i]!=per[G[i][j]]){
				++C[per[G[i][j]]];
                ++R[per[i]];
			}
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值