Tarjan求强连通分量,缩点

强连通分量

是指在一张图图中任意两点都可以两两互相到达.

作用

在一些问题中因为两两可以互相到达的性质,可以将一个强连通分量当做一个大点来做.

Tarjan

tarjan算法求割点的做法相似,遍历时记录一个时间戳和一个low记录不经过祖先节点可以到达到的时间戳最小的点,遍历时再将扫到的点放入一个栈中,并用一个布尔数组记录这些点.
若时间戳与low相等,则不断pop直到当前点被pop,并且pop掉的点在布尔数组中清零,这样避免了被重复利用,被pop掉的点属于同一个强连通分量.

代码

void dfs(int now)
{
	int p,q;
	tim[now]=low[now]=++tt;
	use[now]=1;
	sta.push(now);
	for(p=first[now];p!=-1;p=bn[p].next)
	{
		if(!tim[bn[p].to])
		{
			dfs(bn[p].to);
			low[now]=min(low[now],low[bn[p].to]);
		}
		else if(use[bn[p].to])
		{
			low[now]=min(low[now],tim[bn[p].to]);
		}
	}
	if(low[now]==tim[now])
	{
		num[now]=++lt;
		use[now]=0;
		for(;sta.top()!=now;sta.pop())
		{
			num[sta.top()]=lt;
			use[sta.top()]=0;
		}
		sta.pop();
	}
}

例题 洛谷 P3387 【模板】缩点

题意

给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

做法

因为点,边都可以走多次,因此每个强连通分量都可以被看做一个大点,可以将原图中的所有点都缩成一个大点,再建新图,因为经过缩点,所以新图是一个DAG,再用拓扑排序的方法求最大点权和即可.

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#include<queue>
#define N 10010
#define M 100100
using namespace std;

int n,m,a[M],b[M],first[N],tim[N],low[N],num[N],tt,lt,bb,dn[N],sz[N],d[N],ds[N],ans;
bool use[N];
struct Bn
{
	int to,next;
}bn[M];
stack<int>sta;
queue<int>que;

inline void add(int u,int v)
{
	bb++;
	bn[bb].to=v;
	bn[bb].next=first[u];
	first[u]=bb;
}

void dfs(int now)
{
	int p,q;
	tim[now]=low[now]=++tt;
	use[now]=1;
	sta.push(now);
	for(p=first[now];p!=-1;p=bn[p].next)
	{
		if(!tim[bn[p].to])
		{
			dfs(bn[p].to);
			low[now]=min(low[now],low[bn[p].to]);
		}
		else if(use[bn[p].to])
		{
			low[now]=min(low[now],tim[bn[p].to]);
		}
	}
	if(low[now]==tim[now])
	{
		num[now]=++lt;
		use[now]=0;
		for(;sta.top()!=now;sta.pop())
		{
			num[sta.top()]=lt;
			use[sta.top()]=0;
		}
		sta.pop();
	}
}

int main()
{
	memset(first,-1,sizeof(first));
	int i,j,p,q;
	cin>>n>>m;
	for(i=1;i<=n;i++) scanf("%d",&dn[i]);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&a[i],&b[i]);
		add(a[i],b[i]);
	}
	for(i=1;i<=n;i++) if(!tim[i]) dfs(i);
	for(i=1;i<=n;i++) sz[num[i]]+=dn[i];
	bb=0;
	memset(first,-1,sizeof(first));
	for(i=1;i<=m;i++)
	{
		if(num[a[i]]==num[b[i]]) continue;
		add(num[a[i]],num[b[i]]);
		ds[num[b[i]]]++;
	}
	for(i=1;i<=lt;i++)
	{
		if(!ds[i]) que.push(i),d[i]=sz[i];
	}
	for(;!que.empty();)
	{
		q=que.front();
		que.pop();
		for(p=first[q];p!=-1;p=bn[p].next)
		{
			d[bn[p].to]=max(d[bn[p].to],d[q]+sz[bn[p].to]);
			ds[bn[p].to]--;
			if(!ds[bn[p].to]) que.push(bn[p].to);
		}
	}
	for(i=1;i<=lt;i++) ans=max(ans,d[i]);
	cout<<ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值