受欢迎的牛(洛谷-P2341)题解

题意描述

给定一张有向图,求所有节点都能到达的节点个数。

解题思路

1.tarjan算法求有向图强连通分量

先介绍几个概念:

  • 强连通图:在有向图G中,如果对于每一对vi、vj,vi≠vj,从vi到vj和从vj到vi都存在路径,则称G是强连通图。
  • 强连通分量:有向图的极大强连通子图,称为强连通分量。有向图G的极大强连通子图S,即添加任意顶点都会导致S失去强连通的属性,则称S为G的强连通分量。
  • 缩点:将有向图强连通分量当初一个节点。

 tarjan算法解决寻找强连通分量问题,流程如下:

  • 对所有节点进行深度优先遍历,对每个节点记录dfn及low:  
    • dfn:表示搜遍历到此节点的时间戳。搜索时记录。
    • low:表示此节点能够到达的节点的最小时间戳。初值为dfn,搜索树上子节点回溯后或找到一条连接遍历到但没有被找到在一个强连通分量中的节点的边(返祖边)更新。

 原图

搜索森林

  • 若讨论完所有可以到达的节点后,dfn=low,则这个节点是一个强连通分量的根,与其子树中不在已找到的强连通分量的节点在同一个强连通分量中。
  • 用栈维护:每遍历到一个节点将其加入栈中,当找到一个强连通分量的根时依次弹出节点直到弹出当前节点,所有弹出的节点在一个强连通分量中。

代码如下

int n,m;
int la[N],ne[M],en[M],idx;
int dfn[N],dfn_,low[N];
int s[N],tt;
bool instack[N];
int scc;
int belong[N];

void add(int a,int b)
{
	idx++;
	ne[idx]=la[a];
	la[a]=idx;
	en[idx]=b;
}

void tarjan(int u)
{
	//初始化dfn,low 
	dfn[u]=++dfn_;
	low[u]=dfn[u];
	
	//入栈 
	s[++tt]=u;
	instack[u]=true;
	
	//遍历相邻的边  
	for(int j=la[u];j;j=ne[j])
	{
		int v=en[j];
		if(dfn[v]==0)//没遍历过:遍历并更新low 
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(instack[v])//遍历过但不属于已找到的强连通分量:搜索树的祖宗节点,更新low 
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	
	if(low[u]==dfn[u])//强连通分量的根 
	{
		scc++;
		int v;
		//所有依次弹出栈顶元素,直到当前节点,所有弹出的节点在一个强连通分量中  
		do
		{
			v=s[tt--];
			instack[v]=false;
			belong[v]=scc;
		}while(u!=v);
	}
}

2.缩点

在强连通分量中,任意两点互相可达,于是就将其当作一个点处理。

缩点后原图会变为有向无环图,有更多性质。

一般缩点不需要重新建图,染色即可。

3.有向无环图一个性质

性质:有向无环图中唯一出度为0的节点所有节点可达。

接下来我会对这一性质做出比较严格的证明,但由于这个性质比较显然,可以跳过。

证明:

  • 首先,有向无环图必有一个节点出度为0。
    • 对于节点个数为1的图,由于不能产生环,则不能存在边,所以节点出度为0。
    • 对于节点个数为n(n>1)的图,假设所有节点出度不为0,则对于任意节点,若此节点入度为0,则其它(n-1)个出度不为0的节点构成有向无环图;若此节点入度不为0,则所有从此节点开始的边连接的点不能到达所有此节点结束的边连接的点,把这些点看作一个点,则使x(1\leqslant x<n)个入度不为0的节点构成有向无环图。
    • 综上,结论成立。
  • 然后,如果有向无环图有不止一个节点出度为0,则任何节点不能被其它所有节点到达。
    • 出度为0的点没有从此节点开始的边,每个出度为0的点之间互相不能到达。
  • 最后,有向无环图唯一出度为0的点一定能被其他所有点到达。
    • 假设有一个节点不能到达出度为0的点,则对于由此节点每条路径,所经过的点出度都不为0,由于终点出度不为0,则可以继续走到一个节点,则最长路径的长度无限大,所以一定存在环,不符合无环,故结论成立。

于是对于这道题,只需要缩点后统计节点出度,如果出度为0的点只有一个,则答案为其强连通分量节点个数,否则答案为0.

示例代码

#include<iostream>
#include<algorithm>

using namespace std;

const int N=10005,M=50005;

int n,m;
int la[N],ne[M],en[M],idx;
int dfn[N],dfn_,low[N];
int s[N],tt;
bool instack[N];
int scc;
int belong[N];
int du[N];
int t,ans;

void add(int a,int b)
{
	idx++;
	ne[idx]=la[a];
	la[a]=idx;
	en[idx]=b;
}

void tarjan(int u)
{
	dfn[u]=++dfn_;
	low[u]=dfn[u]; 
	 
	s[++tt]=u;
	instack[u]=true;
	
	for(int j=la[u];j;j=ne[j])
	{
		int v=en[j];
		if(dfn[v]==0)
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(instack[v]) 
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	
	if(low[u]==dfn[u]) 
	{
		scc++;
		int v; 
		do
		{
			v=s[tt--];
			instack[v]=false;
			belong[v]=scc;
		}while(u!=v);
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		add(a,b);
	}
	
	for(int i=1;i<=n;i++)
		if(!belong[i])
			tarjan(i);
	
	for(int i=1;i<=n;i++)
		for(int j=la[i];j;j=ne[j])
			if(belong[i]!=belong[en[j]])
				du[belong[i]]++;
	
	t=0;
	for(int i=1;i<=scc;i++)
		if(!du[i])
		{
			if(t)t=-1;
			else t=i;
		}
	if(t>0)
	{
		for(int i=1;i<=n;i++)
			ans+=belong[i]==t;
	}
	printf("%d",ans);
	
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值