[笔记]: Tarjan算法求有向图的强连通分量

21 篇文章 0 订阅
11 篇文章 0 订阅

所谓Tarjan算法 建议学习算法竞赛入门经典训练指南中的这一章对此的介绍

因为我的解释需要中文十级才看的懂

核心就是dfs中记录一个二元组

一个叫dfn(就是算法训练入门经典中的pre)还有一个叫low

dfn就是在图中dfs时的时间戳

low则是这个点能访问回到的点的dfn的最小值

举个例子

例如

1->4<->5

此时的1 4 5 dfn值分别为123 

当访问到5的时候low值都初始化为自己的dfn本身(自己可以到达自己

因为此时5没有连别的点 dfn(5)会又搜索到4 所以此时的low[5]=min[low[5],dfn[4]]=2(原来是3)

然而4搜不到1 那么4的low值就还是dfn(4) 此时4 5是一个强连通分量


Tarjan算法中需要用一个栈 记录搜到的点 用一个instack标记每个点是否在栈中

还是上面的例子 当搜到5的时候栈里面是 1 4 5

当5 反向到4的时候 如果4 在栈中   就说明4可以搜索到5(核心)

因为5可以到4且4可以到5 那么此时4 5就是强连通分量 把4 5弹出 连通分量的数目加一


例题就是poj的2186

===传送门===

求出受欢迎的牛的个数 

其实就是求某个出度为0的连通的元素的个数

先套模板Tarjan求强联通分量

求这个个数时 如果某个点连到了别的连通分量 那么这个点所在的连通分量都不是受欢迎的(这个连通分量的牛认为别人联通分量的牛受欢迎 那么这个组就不收欢迎了)

注意!!WA了刚好10次。。血的教训

1.根据题意这个出度为0的连通分量最多只有一个 如果大于1个 则直接输出0

2.还有就是此题是多组输入数据。。。。(调了一个晚上)

下面代码 虽然因为后面调试开了好多好多数组 改的不成样子了

但是Tarjan模板还是可以将就着看的 


#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int Stack[100005],top,num,ans=0,cnt=0,dfn[100005],low[10005],head[10005],id[10005];
int answer=0;
bool inStack[10005],failed[10005];
struct node{int to,next;}e[100005];
void insert(int x,int y){
	e[++cnt].to=y;e[cnt].next=head[x];head[x]=cnt;
}
void tarjan(int x){
	num++;
	Stack[++top]=x;inStack[x]=1;
	dfn[x]=num;low[x]=num;
	for(int i=head[x];i;i=e[i].next){
		int k=e[i].to;
		if(!dfn[k]){
			tarjan(k);
			low[x]=min(low[x],low[k]);
		}
		else{
            if(inStack[k])
			low[x]=min(low[x],dfn[k]);
		}
	}
	if(low[x]==dfn[x]){
		int t=Stack[top];ans++;
		while(t!=x)
		{
            inStack[t]=0;
			id[t]=ans;
            t=Stack[--top];
		}
		id[x]=ans;top--;
	}
}
int main(){
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF) 
	{
		memset(head,0,sizeof(head));
		memset(Stack,0,sizeof(Stack));
		memset(failed,0,sizeof(failed));
		memset(inStack,0,sizeof(inStack));
		memset(dfn,0,sizeof(dfn));
		memset(low,0,sizeof(low));
		memset(id,0,sizeof(id));
		top=0;num=0;
		cnt=0;ans=0;answer=0;
		int x,y;
		for(int i=1;i<=m;i++){
			scanf("%d%d",&x,&y);
			insert(x,y);
		}
		for(int i=1;i<=n;i++)
		{
			if(!dfn[i])
				tarjan(i);
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=head[i];j;j=e[j].next){
				int k=e[j].to;
				if(id[k]!=id[i]){
					failed[id[i]]=1;
				}
			}
		}
		int tmp=0;
		for(int i=1;i<=ans;i++){
			if(!failed[i]) tmp++;
		}
		if(tmp>1){
			puts("0");continue; 
		}
		for(int i=1;i<=n;i++){
			if(!failed[id[i]]) answer++;
		}
		printf("%d\n",answer);
	}
	return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值