图论学习笔记:Tarjan强连通分量

Tarjan强连通分量

首先这个算法的作用就是缩点,将每一个强连通分量缩成一个点,从而保证缩点之后的图是一个DAG(有向无环图),然后我们再在这张图上进行各种各样的操作。

强连通分量

强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。

强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。

即强连通分量内的任意两点可相互抵达

代码实现

详细注解代码

//tarjan 
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N=1e4+10;
const int M=2e5+10;
int n,m;
int head[N],ver[M],Next[M],tot=-1;
//原始建图 
int head2[N],ver2[M],edge2[M],Next2[M];
//缩点后建图 

int w[N]; //每个点的点权 
int rdu[N]; //这里是用来确定起点的
int cdu[N]; //这里是用来确定终点的  

//spfa
int dist[N],q[N*2],l=0,r=0;

//tarjan变量集合 
int idc=0,top=0;  //时间戳 栈顶 
int dfn[N]; //记录图的dfs序 
int low[N]; //记录x所能到达的dfs序最小的子节点 
int stack[N]; //栈 维护已经被遍历到 但是没有被确定属于哪一个强连通分量的点 

int res[N]; //缩点之后的点权 (视题目而定) 
int scc[N]; //记录缩点后x属于哪一个节点 即点x的强连通分量 
int scnt=0; //缩点后节点(强连通分量)个数 
//
bool vis[N];

//两次建图 
void add(int x,int y)
{
	++tot;
	ver[tot]=y;
	Next[tot]=head[x];
	head[x]=tot;
}
void ADD(int x,int y,int z)
{
	++tot;
	ver2[tot]=y;
	edge2[tot]=z;
	Next2[tot]=head2[x];
	head2[x]=tot;
}

void init()
{	
	memset(dist,0x3f3f3f3f,sizeof(dist));
	memset(head2,-1,sizeof(head2));	
	memset(head,-1,sizeof(head));
	memset(vis,0,sizeof(vis));
	memset(rdu,0,sizeof(rdu));
	memset(cdu,0,sizeof(cdu));
}
int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+(ch-'0');
		ch=getchar();
	}
	return x*f;
}

void tarjan(int x)
{
	vis[x]=1; //dfs标记 
	
	++idc;
	dfn[x]=idc;
	//记录dfs序 
	
	low[x]=idc;
	//第一次访问到该节点时 该节点所能到达的dfs序最小的点就是它自己 
	
	stack[++top]=x; //入栈 
	
	//遍历邻边 
	for(int i=head[x];i!=-1;i=Next[i])
	{
		int y=ver[i];
		if(dfn[y]==0) //这个点没访问过 (相当于vis[y]==0)
		{
			tarjan(y); //访问 
			low[x]=min(low[x],low[y]); 
			//回溯时更新x所能到达的dfs序最小的点 
			//因为y是x的子节点 所以y可以到达的点 x也一定可以到达 
		}
		else if(vis[y]==1) 
		//该点已被访问过 即存在x->y的一条后向边 (即从x指向某个dfs序比x小的点 这说明存在环)
		//同样需要更新x所能到达的dfs序最小的点 
		{
			low[x]=min(low[x],low[y]);
		}
	}
	//dfs跑完 回溯 
	//此时对于栈内的点x来说 它上面的点都是它的子节点
	
	//如果x所能到达的dfs序最小的点就是它自己 
	//那它和自己的 子节点(栈内x及它上面的点) 构成一个环(强连通分量) 
	//否则说明这个点在强连通分量中 但不是这个强连通分量中dfs序最小的点 
	if(low[x]==dfn[x]) //一个强连通分量 
	{
		++scnt; //强连通分量++
		//x和它前面的点构成一个强连通分量 将它们出栈 并记录 
		int sum=0; //记录该强连通分量的点权(题目需要) 
		do //遍历x前面所有的的点 它们属于同一个强连通分量 
		{
			int y=stack[top];
			vis[y]=0; //回溯 (一定记得写) 
			scc[y]=scnt; //它们属于同一个强连通分量 
			sum+=w[y];//记录该强连通分量的点权
		}while(stack[top--]!=x);
		res[scnt]=sum;//记录该强连通分量的点权
	} 
}
void spfa(int u) //spfa
{
	dist[u]=0;
	q[r++]=u;
	while(l<r)
	{
		int x=q[l++];
		for(int i=head2[x];i!=-1;i=Next2[i])
		{
			int y=ver2[i];
			int z=edge2[i];
			if(dist[y]>dist[x]+z)
			{
				dist[y]=dist[x]+z;
				q[r++]=y;
			}
		}
	}
}
int main()
{
	init(); //初始化 
	
	//输入部分 
	n=read();
	m=read();
	for(int i=1;i<=n;i++)
	{
		w[i]=read();
	}
	for(int i=1;i<=m;i++)
	{
		int x=read();
		int y=read();
		add(x,y);
	}
	//输入部分 
	
	//缩点 
	for(int i=1;i<=n;i++)
	{
		if(dfn[i]==0)
		{
			//这个图可能是森林 对每一个连通块都跑一遍缩点 
			tarjan(i);
		}
	}
	//缩点
	
	//重新建图
	//枚举每个点的边 按照强连通分量建图(这样会有许多重边 但链式前向星无视重边) 
	for(int u=1;u<=n;u++)
	{
		for(int i=head[u];i!=-1;i=Next[i])
		{
			int y=ver[i];
			if(scc[u]!=scc[y]) //不在同一个强连通分量里 可以连边 
			{
				ADD(scc[u],scc[y],-res[scc[y]]);
				//点权转边权 用的肯定是y的点权了 (spfa跑最长路 建负边权)
				
				rdu[scc[y]]++; //记录一下入度 
				cdu[scc[u]]++; //记录一下出度
			}
		}
	}
	//这样一张缩点之后的DAG图就建好了 
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值