【学习笔记】Tarjan算法

算法简介

强连通:有向图 G 中任意两个结点连通
强连通分量(SCC):极大的强连通子图。

Tarjan算法的原理是利用DFS生成树求强连通分量。
其中为图中每个节点 u u u维护了以下变量:

变量解释
dfn[u]节点 u u u在深度优先搜索中的次序,简称dfs序
low[u]以节点 u u u为根的子树中,通过一条不在搜索树上的边到达的节点dfn的最小值

在这里插入图片描述
在这里插入图片描述

实例

求割点

P3388【模板】割点(割顶)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;

const int M =2e5+7;
const int N =2e4+7;

int e[M],ne[M],idx,h[N];
int dfn[N],low[N],now,root;
int tot;
bool cut[N];

void add(int u,int v)
{
	e[++idx]=v;
	ne[idx]=h[u];
	h[u]=idx;
}

void dfs(int u)
{
	dfn[u]=low[u]=++now;
	int flag=0;
	for(int i=h[u];i;i=ne[i])
	{
		int v=e[i];
		if(!dfn[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
			//割点判定法则
			if(dfn[u]<=low[v]&&(++flag>=2||u!=root)&&!cut[u])
				cut[u]=1,tot++;
		}
		else low[u]=min(low[u],dfn[v]);
	}
}

int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++)
		scanf("%d%d",&u,&v),add(u,v),add(v,u);
	for(int i=1;i<=n;i++)
		if(!dfn[i])root=i,dfs(i);
	printf("%d\n",tot);
	for(int i=1;i<=n;i++)
		if(cut[i])printf("%d ",i);
	return 0;
}

求强连通分量并缩点

P3387 【模板】缩点

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;

const int N = 1e4 + 7;
const int M = 1e5 + 7;

struct edge{int from,to,ne;}edges[M];
int he[N],eidx;
void add_edge(int u,int v)
{
	edges[++eidx].from=u;
	edges[eidx].to=v;
	edges[eidx].ne=he[u];
	he[u]=eidx;
}

int h[N],ne[M],e[M],w[N],ru[N],idx;
void add(int u,int v)
{
	e[++idx]=v;
	ne[idx]=h[u];
	h[u]=idx;
	ru[v]++;
}

int dfn[N],low[N],stk[N],sidx,times;
int id[N];
bool vis[N];

void dfs(int u)
{
	dfn[u]=low[u]=++times;
	stk[++sidx]=u,vis[u]=1;
	for(int i=he[u];i;i=edges[i].ne)
	{
		int v=edges[i].to;
		if(!dfn[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v])low[u]=min(low[u],dfn[v]);
	}
	//一个强连通分量的根节点
	if(dfn[u]==low[u])
	{
		while(sidx)
		{
			int top=stk[sidx--];
			vis[top]=0;
			id[top]=u;
			if(top==u)break;
			//强连通分量中所有的边权缩为一个点
			w[u]+=w[top];
		}
	}
}

int dist[N];

int main()
{
	int n,m,u,v;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&w[i]);
	for(int i=1;i<=m;i++)scanf("%d%d",&u,&v),add_edge(u,v);
	for(int i=1;i<=n;i++)if(!dfn[i])dfs(i);

    //重新建图进行缩点
	for(int i=1;i<=m;i++)
	{
		int u=id[edges[i].from],v=id[edges[i].to];
		if(u!=v)add(u,v);
	}
    
    //本题实际上不需要拓补排序求dp顺序,因为缩点后的图是一个DAG
	queue<int> q;
	for(int i=1;i<=n;i++)
		if(!ru[i])q.push(i),dist[i]=w[i];
	while(q.size())
	{
		int top=q.front();q.pop();
		for(int i=h[top];i;i=ne[i])
		{
			int v=e[i];
			dist[v]=max(dist[v],dist[top]+w[v]);
			if(--ru[v]==0)q.push(v);
		}
	}

	int ans=0;
	for(int i=1;i<=n;i++)
		ans=max(ans,dist[i]);
	cout<<ans<<endl;

	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值