强连通分量

强连通分量

目录

  • 基本概念

  • \(Kosaraju\)算法

  • \(Tarjan\)算法

  • 例题讲解

  • 题目推荐

  • 学习资源


基本概念

  • 连通图

无向图中,从任意点\(i\)可以到达任意点\(j\)

  • 强连通图

有向图中,从任意点\(i\)可以到达任意点\(j\)

  • 弱连通图(了解即可)

人为地将有向图看做无向图后,从任意点\(i\)可以到达任意点\(j\)

  • 极大强连通子图

\(G\)是一个极大强连通子图,当且仅当\(G\)是一个强连通子图且不存在另一个强连通子图\(G'\),使得\(G\)\(G'\)的真子集

  • 强连通分量

有向非强连通图的极大强连通子图

因为来现实生活中有意义的强连通图很少,所以一般讨论的都是强连通分量

若将有向图中的强连通分量都缩为一个点,则原图就会变成一个DAG(有向无环图),如下图(1)-图(2)所示:

图(1)

图(2)

来讲(啰嗦 )一下,因为强连通分量相当于环啊,将环缩为点之后那就是无环图咯,这个很好想,证明的话反证法即可

  • 强连通分量的应用
  1. 有向图的缩点:见上图示

  2. 解决\(2-SAT\)问题(还没学....之后更新啊qwq)


\(Kosaraju\)算法

基于两次\(DFS\)的有向图强连通分量算法,时间复杂度为\(O(n+m)\)

  • 算法框架
  1. 对原图\(G\)进行\(DFS\),记录每个节点访问完的顺序\(dfn[i]\)并将点压入

  2. 选择最晚访问完的节点对\(G\)反向图进行第二次\(DFS\),删除能够遍历到的节点,每次遍历到的一坨(或一个)节点构成一个强连通分量

  3. 一直执行\((2)\)操作,直到所有节点都二次遍历完

  • 例子图示

第一次\(DFS\)顺序:\(3->2->1->4\)!)

第二次\(DFS\)顺序:\(4,2->1,3\) (一个逗号前为一个强连通分量)

  • 代码函数段

个人认为比接下来的\(Tarjan\)好理解,但是使用的更多的还是扩展性更强的\(Tarjan\)

inline void dfs1(int x) {
    vis[x]=1;
    for(register int i=1;i<=n;i++) {
        if(!vis[i]&&map[x][i]) dfs1(i);
        dfn[++t]=x;
    }
}

inline void dfs2(int x) {
    vis[x]=t;
    for(register int i=1;i<=n;i++) {
        if(!vis[x]&&map[i][x]) dfs2(i);
    }
}

inline void ko() {
    t=0;
    for(register int i=1;i<=n;i++) {
        if(!vis[i]) dfs1(i);
    }
    memset(vis,0,sizeof(vis));
    t=0;
    for(register int i=n;i>=1;i--) {
        if(!vis[dfn[i]]) {
            t++;
            dfs2(dfn[i]);
        }
    }
}

\(Tarjan\)算法

基于一次\(DFS\)的算法,时间复杂度也是\(O(n+m)\)

\(kosaraju\)算法的\(DFS\)不同,\(Tarjan\)\(DFS\)更类似于树的后序遍历

上图理解吧:


图片截屏自我的学习视频,在文章最后会贴上,侵删!

至于很多博客讲的四种边(树枝边、前向边、后向边、横叉边),我个人认为是没有必要掌握的,了解一下就可以了

  • 所需变量
  1. \(dfn[i]\):表示节点\(i\)的遍历顺序(同\(Kosaraju\)算法)

  2. \(low[i]\):表示节点\(i\)可回溯到的最早遍历时间(初始与\(dfn[i]\)一致)

  3. \(fir[i]=x\):表示节点\(i\)和节点\(x\)同属于一个强连通分量

  4. \(q[top]\):手写栈qwq

5. 以及一啪啦的变量(可麻烦了)

  • 算法框架

设当前点为\(x\)

  1. 初始\(dfn[x]\)=\(low[x]\)=\(++ti\)(时间戳)

  2. 入栈当前点并标记为访问过

  3. 遍历与\(x\)相连的点,进行下一层的\(DFS\),然后更新\(low[x]\)

  4. 遍历完后,如果当前\(x\)\(dfn\)==\(low\),则可以弹出一个强连通分量了

可能有点抽象,如果不好理解,可以先跳到文末点击视频链接,里面讲得敲详细qwq

  • 代码实现

以下代码是根据洛谷P3387 【模板】缩点 这道题编的,大家注意区分啊

另外,下面这份代码涉及到的拓扑排序,有兴趣的可以看我的另一篇博客

(PS:变量申请那块奇丑...轻喷)

#include <bits/stdc++.h>
using namespace std;
int n,m,u,v,tot,ans,a[520010],in[520010],fir[520010],head[520010];
int ti,top,num,q[520010],vis[520010],dis[520010],sum[520010],dfn[520010],low[520010];

struct node {  
	int to,net,fro;
} e[520010],es[520010];

inline void add(int u,int v) {
	e[++tot].to=v;
	e[tot].fro=u;
	e[tot].net=head[u];
	head[u]=tot;
}

inline void tarjan(int x) {
	dfn[x]=low[x]=++ti;
	q[++top]=x;
	vis[x]=1;
	for(register int i=head[x];i;i=e[i].net) {
		int v=e[i].to;
		if(!dfn[v]) {
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else {
			if(vis[v]) low[x]=min(low[x],dfn[v]);
		}
	}
	if(low[x]==dfn[x]) {
		int v=q[top];
		while(top) {
			fir[v]=x;
			vis[v]=0;
			if(v==x) {
				top--;
				break;
			}
			a[x]+=a[v];
			v=q[--top];
		}
	}
}

inline void topo() {  //拓扑排序求最长路
	queue<int> q;
	for(register int i=1;i<=n;i++) {
		dis[i]=a[i];
		if(!in[i]&&fir[i]==i) q.push(i);
	}
	while(!q.empty()) {
		int x=q.front();
		q.pop();
		for(register int i=head[x];i;i=es[i].net) {
			int v=es[i].to;
			dis[v]=max(dis[v],dis[x]+a[v]);
			if(--in[v]==0) q.push(v);
		}
	}
}

int main() {
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	for(register int i=1;i<=n;i++) {
		if(!dfn[i]) tarjan(i);
	}
	tot=0;
	memset(vis,0,sizeof(vis)); 
	memset(head,0,sizeof(head));
	for(register int i=1;i<=m;i++) {
		u=fir[e[i].fro];
		v=fir[e[i].to];
		if(u!=v) {
			in[v]++;
			es[++tot].to=v;
			es[tot].net=head[u];
			es[tot].fro=u;
			head[u]=tot;
		}
	}
	topo();
	for(register int i=1;i<=n;i++) ans=max(ans,dis[i]);
	printf("%d",ans);
	return 0;
}

例题讲解

洛谷P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G

个人认为很模板的题,思路稍微转换一下就出来了,写个小题解当做例题讲解qwq

  • 分析

奶牛们之间的喜爱是单向的、可传递的,那么将文字描述抽象一下,即:

奶牛们是点,喜爱关系是单向边,整个关系则构成了有向图

相互喜爱的一群牛组成一个集合,则\(N\)头牛可以划分为\(S\)个集合(缩点的思想,缩环为点

集合与集合之间也存在喜爱关系,则原图就转化为了DAG(有向无环图)

这个时候我们就需要思考一下:到底什么样的牛是所有牛喜欢的那个?

显然:是出度为\(0\)的集合中的牛

为什么?因为出度为\(0\)则说明这个集合不喜爱其他的牛,则满足所有牛都喜欢这头牛(有点绕,画一下图会好一点,作者懒不想画了QAQ)

由此,我们就将问题转换为了:缩点,然后求出度为\(0\)的点

现在给出以上思路的\(AC\)程序:

#include <bits/stdc++.h>
using namespace std;
int n,m,u,v,tot,num,out[520010],sum[520010],head[520010];
int ti,top,q[520010],val[520010],fir[520010],vis[520010],dfn[520010],low[520010];

struct node {
	int to,net,fro;
} e[520010];

inline void add(int u,int v) {
	e[++tot].to=v;
	e[tot].fro=u;
	e[tot].net=head[u];
	head[u]=tot;
}

inline void tarjan(int x) {
	dfn[x]=low[x]=++ti;
	q[++top]=x;
	vis[x]=1;
	for(register int i=head[x];i;i=e[i].net) {
		int v=e[i].to;
		if(!dfn[v]) {
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else {
			if(vis[v]) low[x]=min(low[x],dfn[v]);
		}
	}
	if(low[x]==dfn[x]) {
		int v=q[top];
		while(top) {
			fir[v]=x;
			vis[v]=0;
			if(v==x) {
				top--;
				break;
			}
			val[x]+=val[v];
			v=q[--top];
		}
	}
}

int main() {
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=n;i++) val[i]=1;
	for(register int i=1;i<=m;i++) {
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	for(register int i=1;i<=n;i++) {
		if(!dfn[i]) tarjan(i);
	}
	for(register int i=1;i<=m;i++) {
		u=fir[e[i].fro];
		v=fir[e[i].to];
		if(u!=v) out[u]++;
	}
	for(register int i=1;i<=n;i++) {
		if(!out[i]&&fir[i]==i) {
			sum[++num]=val[i];
		}
	}
	if(num==1) printf("%d",sum[num]);
	else if(num>=2) puts("0");
	return 0;
}

PS:以下内容为我的无脑暴力骗分代码,可以跳过(52\(pts\)真香)

#include <bits/stdc++.h>
using namespace std;
int n,m,u,v,sum,flag;
int in[520010],out[520010];

int main() {
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d",&u,&v);
		out[u]++;
		in[v]++;
	}
	for(register int i=1;i<=n;i++) {
		if(out[i]==0) sum++;
	}
	if(sum==1) printf("1");
	else if(sum>=2) printf("0");
	else if(sum==0){
		for(register int i=1;i<=n;i++) {
			if(out[i]!=in[i]) {
				flag=true;
				break;
			}
		}
		if(flag==false) printf("%d",n);
	}
	return 0;
}

例题讲解(正文)完毕~~


题目推荐

  1. 洛谷P3387 【模板】缩点

  2. 洛谷P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G

  3. 洛谷P4782 【模板】2-SAT问题

  4. 洛谷P1407 [国家集训队]稳定婚姻


学习资料

  1. \(B\)站学习视频,讲得真的很清晰,适合初学者

  2. \(Tarjand\)算法,图示详细但有点丑


posted @ 2020-07-10 16:12  Eleven谦  阅读(428)  评论(1编辑  收藏  举报