DAY_11(强连通分量)

求解强连通分量有两种算法,tarjan和kosaraju

首先来俩概念:

强连通:在有向图G中,若两个点u、v是互相可达的,则称u和v是连通的。如果G中任意两个点都是互相可达的,称G是强连通图

强连通分量:如果一个有向图G不是强连通图,那么可以把它分成多个子图,其中每个子图的内部是强连通的,而且这些子图已经扩展到最大,不能与子图外的任意点强连通,称这样一个“极大强连通”子图是G的一个强连通分量(Strongly Connected Component)

有个常见的问题:求G中有多少个scc?

我们先来尝试着把每个scc抽象成一个个的群岛,群岛由有着很多或很少的小岛组成,群岛内部连通;群岛与其他群岛之间有单向道路连接,且不会形成环路。然后将每一个群岛虚拟成一个个点,最后得到一个有向无环图GG,GG中点的数量即为scc数量(还能推出:每个群岛挖掉后,并不会影响其他岛内部的连通性)

那么一开始想到的一定是暴力!!!(暴力不可取。。。)

暴力思路:对每个点求连通性,然后进行比较,互相连通的点就组成了scc(dfs或bfs可实现)

。。。时间复杂度:O(n^2+m)

下面介绍Kosaraju和Tarjan算法(时间复杂度:O(n+m)

Kosaraju

原理:

1、将有向图G的所有边反向构建反图rG,rG不会改变原图G的强连通性。(人话:G的scc数量和rG的数量相同)

2、对G和rG各做一次dfs,可以确定scc数量

有一个我之前特别困惑的点:dfs干啥用的?

——对虚拟点进行拓扑排序,排序后将优先级最高的虚拟点进行反dfs,就可以求出被群岛与外界隔离的那些小岛了

模版在下面\forall \forall \forall \forall

Tarjan

Kosaraju是在图中一个一个将scc给挖出来,而Tarjan能在一次dfs就将所有点按照scc分开

其实理解了Kosaraju,来理解Tarjan也不是很难(个人意见)

有几个变量需要注意:

low[i]:表示能返回到的最远祖先

num[i]:表示第几次循环遍历到i(不太重要的样子

步骤:首先dfs,赋值每个点的low,然后判断low是否与该点的num值相等,如果相等,那么这个点就是自己的祖先点。额。就没了。。(是不是挺简单的~~)

下面上题目::

B3609 [图论与代数结构 701] 强连通分量

一道模版题,下面依次提供Kosaraju和Tarjan两种做法:

Kosaraju::

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
vector<int>g[N],scc[N];
stack<int>st;
int n,m;
int f[N];
int low[N];
int num[N];
int dfn,cnt;
void dfs(int u)
{
	dfn++;
	low[u]=num[u]=dfn;
	st.push(u);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(!num[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!f[v])
		low[u]=min(low[u],num[v]);
	}
	if(low[u]==num[u])
	{
		cnt++;
		while(!st.empty())
		{
			int k=st.top();
			f[k]=cnt;
			st.pop();
			scc[cnt].push_back(k);
			if(u==k)
			break;
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
	}
	memset(low,0,sizeof(low));
	memset(num,0,sizeof(num));
	memset(f,0,sizeof(f));
	cnt=dfn=0;
	for(int i=1;i<=n;i++)
	if(!num[i])
	dfs(i);
	cout<<cnt<<endl;
	for(int i=1;i<=n;i++)
	sort(scc[i].begin(),scc[i].end());
	for(int i=1;i<=n;i++)
	if(num[f[i]])
	{
		for(int j=0;j<scc[f[i]].size();j++)
		cout<<scc[f[i]][j]<<" ";
		cout<<endl;
		num[f[i]]=0;
	}
	return 0;
}

Tarjan::

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int m,n;
vector<int> g[N];
stack<int> st;
int low[N],num[N],f[N];
int dfn,cnt;
int ans;
int out[N];
int mmp[N];
void dfs(int u)
{
	dfn++;
	low[u]=num[u]=dfn;
	st.push(u);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(!num[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!f[v])
		low[u]=min(low[u],num[v]);
	}
	if(low[u]==num[u])
	{
		cnt++;
		while(!st.empty())
		{
			int k=st.top();
			st.pop();
			f[k]=cnt;
			mmp[cnt]++;
			if(u==k)
			break;
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
	}
	memset(num,0,sizeof(num));
	memset(low,0,sizeof(low));
	memset(f,0,sizeof(f));
	cnt=dfn=0;
	for(int i=1;i<=n;i++)
	if(!num[i])
	dfs(i);
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<g[i].size();j++)
		if(f[i]!=f[g[i][j]])
		out[f[g[i][j]]]++;
	}
	for(int i=1;i<=cnt;i++)
	if(!out[i])
	ans++;
	cout<<ans<<endl;
}

P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G

稍微带了点变化,最终是求在只有一个点入度为0的强连通图内点的数目,如果有多个点入度为0,则直接输出0(说明有某点不能连通)

代码:(Kosaraju做法)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
vector<int> g1[N],g2[N];
stack<int> st;
int n,m;
int in[N];
int f[N];
int cnt;
int mmp[N];
int ans;
int num;
bool vis[N];
void dfs1(int u)
{
	vis[u]=true;
	for(int i=0;i<g1[u].size();i++)
	if(!vis[g1[u][i]])
	dfs1(g1[u][i]);
	st.push(u);
}
void dfs2(int u)
{
	if(vis[u])
	return ;
	f[u]=cnt;
	num++;
	vis[u]=true;
	for(int i=0;i<g2[u].size();i++)
	if(!vis[g2[u][i]])
	dfs2(g2[u][i]);
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g1[u].push_back(v);
		g2[v].push_back(u);
	}
	for(int i=1;i<=n;i++)
	if(!vis[i])
	dfs1(i);
	memset(vis,false,sizeof(vis));
	while(!st.empty())
	{
		int k=st.top();
		st.pop();
		if(!vis[k])
		{
			num=0;
			cnt++;
			dfs2(k);
			mmp[cnt]=num;
		}
	}
	for(int i=1;i<=n;i++)
	for(int j=0;j<g2[i].size();j++)
	if(f[i]!=f[g2[i][j]])
	in[f[g2[i][j]]]++;
	for(int i=1;i<=cnt;i++)
	{
		if(in[i]==0)
		//入度为0的点,f[i]代表编号,需要找f[i]相同的累加 
		{
			if(ans)
			{
				cout<<0<<endl;
				return 0;
			}
			ans=mmp[i];
		}
	}
	cout<<ans<<endl;
	return 0;
}

Tarjan(做法):

 

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int m,n;
vector<int> g[N];
stack<int> st;
int low[N],num[N],f[N];
int dfn,cnt;
int ans;
int in[N];
int mmp[N];
void dfs(int u)
{
	dfn++;
	low[u]=num[u]=dfn;
	st.push(u);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(!num[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!f[v])
		low[u]=min(low[u],num[v]);
	}
	if(low[u]==num[u])
	{
		cnt++;
		while(!st.empty())
		{
			int k=st.top();
			st.pop();
			f[k]=cnt;
			mmp[cnt]++;
			if(u==k)
			break;
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
	}
	memset(num,0,sizeof(num));
	memset(low,0,sizeof(low));
	memset(f,0,sizeof(f));
	cnt=dfn=0;
	for(int i=1;i<=n;i++)
	if(!num[i])
	dfs(i);
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<g[i].size();j++)
		if(f[i]!=f[g[i][j]])
		in[f[i]]++;
	}
	for(int i=1;i<=cnt;i++)
	{
		if(in[i]==0)
		{
			if(ans)
			{
				cout<<0<<endl;
				return 0;
			}
			ans=mmp[i];
		}
	}
	cout<<ans<<endl;
}

P2002 消息扩散

本题最后求出度为0的scc有多少个

代码:(Kosaraju)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
vector<int> g1[N],g2[N];
stack<int> st;
int n,m;
int in[N],out[N];
int f[N];
int cnt;
int mmp[N];
int ans;
int num;
bool vis[N];
void dfs1(int u)
{
	vis[u]=true;
	for(int i=0;i<g1[u].size();i++)
	if(!vis[g1[u][i]])
	dfs1(g1[u][i]);
	st.push(u);
}
void dfs2(int u)
{
	if(vis[u])
	return ;
	f[u]=cnt;
	num++;
	vis[u]=true;
	for(int i=0;i<g2[u].size();i++)
	if(!vis[g2[u][i]])
	dfs2(g2[u][i]);
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g1[u].push_back(v);
		g2[v].push_back(u);
	}
	for(int i=1;i<=n;i++)
	if(!vis[i])
	dfs1(i);
	memset(vis,false,sizeof(vis));
	while(!st.empty())
	{
		int k=st.top();
		st.pop();
		if(!vis[k])
		{
			num=0;
			cnt++;
			dfs2(k);
			mmp[cnt]=num;
		}
	}
	for(int i=1;i<=n;i++)
	for(int j=0;j<g2[i].size();j++)
	if(f[i]!=f[g2[i][j]])
	out[f[i]]++;
	for(int i=1;i<=cnt;i++)
	if(!out[i])
	ans++;
	cout<<ans<<endl;
	return 0;
}

P2863 [USACO06JAN] The Cow Prom S

群岛内小岛数>1的scc个数

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int m,n;
vector<int> g[N];
stack<int> st;
int low[N],num[N],f[N];
int dfn,cnt;
int ans;
int out[N];
int mmp[N];
void dfs(int u)
{
	dfn++;
	low[u]=num[u]=dfn;
	st.push(u);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(!num[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!f[v])
		low[u]=min(low[u],num[v]);
	}
	if(low[u]==num[u])
	{
		cnt++;
		while(!st.empty())
		{
			int k=st.top();
			st.pop();
			f[k]=cnt;
			mmp[cnt]++;
			if(u==k)
			break;
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
	}
	memset(num,0,sizeof(num));
	memset(low,0,sizeof(low));
	memset(f,0,sizeof(f));
	cnt=dfn=0;
	for(int i=1;i<=n;i++)
	if(!num[i])
	dfs(i);
	for(int i=1;i<=cnt;i++)
	if(mmp[i]>1)
	ans++;
	cout<<ans<<endl;
}

P2746 [USACO5.3] 校园网Network of Schools

A任务:入度为0的scc个数

B任务:入度为0和出度为0的scc个数的较大值

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int m,n;
vector<int> g[N];
stack<int> st;
int low[N],num[N],f[N];
int dfn,cnt;
int ans1,ans2;
int out[N],in[N];
void dfs(int u)
{
	dfn++;
	low[u]=num[u]=dfn;
	st.push(u);
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(!num[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!f[v])
		low[u]=min(low[u],num[v]);
	}
	if(low[u]==num[u])
	{
		cnt++;
		while(!st.empty())
		{
			int k=st.top();
			st.pop();
			f[k]=cnt;
			if(u==k)
			break;
		}
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int v;
		while(1)
		{
			cin>>v;
			if(v==0)
			break;
			g[i].push_back(v);
		}
	}
	memset(num,0,sizeof(num));
	memset(low,0,sizeof(low));
	memset(f,0,sizeof(f));
	cnt=dfn=0;
	for(int i=1;i<=n;i++)
	if(!num[i])
	dfs(i);
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<g[i].size();j++)
		if(f[i]!=f[g[i][j]])
		{
			out[f[i]]++;
			in[f[g[i][j]]]++;
		}
	}
	ans1=0;
	ans2=0;
	for(int i=1;i<=cnt;i++)
	{
		if(!out[i])
		ans2++;
		if(!in[i])
		ans1++;
	}
	cout<<ans1<<endl;
	if(cnt==1)
	cout<<0<<endl;
	else
	cout<<max(ans1,ans2)<<endl;
}

有兴趣建议看看P1073。。。

完结撒花~~~~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值