首页 > 其他分享 >强连通分量(tarjan)

强连通分量(tarjan)

时间:2024-09-04 21:04:58浏览次数:10  
标签:tarjan int 连通 edge ++ dfn low 分量

前言

首先你要知道什么是强连通分量再来不会的话我给你链接啊!

好像上面那个链接已经替代了我 :)

tarjan

tarjan 这个算法的演示图比较复杂,我推荐看这篇博客,这时你肯定要问了,你都推荐别人的博客了,那我看你干嘛,别急,他没给你代可以给你!

我们用 \(dfn[x]\) 表示点 \(x\) dfs 序(dfs遍历时访问的顺序),我们又用 \(low[x]\) 表示 \(x\) 点可以到达最小的 dfs 序,我们又用栈来储存每个点,当遇到环时为了输出当前的强连通分量。

dfn[x]=low[x]=++t;//dfs序
s[++top]=x;//数组模拟栈

然后我们就枚举出边,如果下一个点没遍历过,递归,更新 low,否则直接更新 low ,

for(int i=head[x];i!=-1;i=edge[i].next){
	int y=edge[i].to;
	if(dfn[y]==0){
		tarjan(y);
		low[x]=min(low[x],low[y]);//与那个点的low比较
	}
	else{
		low[x]=min(low[x],dfn[y]);//因为已经又dfn所以直接比较
	}
}

如果 \(dfn[x]\) 与 \(low[x]\) 相等,说明找到了强连通分量的根(开始节点),然后我们就输出这个强连通分量就好了。

if(dfn[x]==low[x]){
	int b;
	do{
		b=s[top];
		cout<<b<<" ";
	}while(x!=s[top--]);
	cout<<"\n";
}

完整代码:

void tarjan(int x){
	dfn[x]=low[x]=++t;
	s[++top]=x;
	for(int i=head[x];i!=-1;i=edge[i].next){
		int y=edge[i].to;
		if(dfn[y]==0){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else{
			low[x]=min(low[x],dfn[y]);
		}
	}
	if(dfn[x]==low[x]){
		int b;
		do{
			b=s[top--];
			cout<<b<<" ";
			if(b==x){
				break;
			}
		}while(x!=b);
		cout<<"\n";
	}
}
void tarjan(int x){
	dfn[x]=low[x]=++t;
	s[++top]=x;
	vis[x]=1;
	
	for(int i=head[x];i!=-1;i=edge[i].next){
		int y=edge[i].to;
		if(dfn[y]==0){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y]){
			low[x]=min(low[x],dfn[y]);
		}
	}
	if(dfn[x]==low[x]){
		int b;
		do{
			b=s[top--];
			of[b]=x;
			vis[b]=0;
			if(b==x){
				break;
			}
			val[x]+=val[b];
		}while(x!=b);
	}
}

这样 tarjan 就学会了吧,如果没有可以多看几篇题解。

缩点

例题 1.洛谷 P3387

例题

学 tarjan 肯定要做这个啊!

其实还是要看看题解学了

我还是太菜,没法直接讲清楚 :( 但其实就是 tarjan 缩点后拓扑对路径做 dp 求最大权值和(半天说了个废话)

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+4;
const int M=1e5+5;
int n,m;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}



int cnt=0,sum=0;
struct Edge{
	int from,to,next;
}edge[2*M],ed[2*M];
int head[N],h[N];
int in[N];

void add_edge(int u,int v){
	edge[cnt].to=v;
	edge[cnt].from=u;
	edge[cnt].next=head[u];
	head[u]=cnt++;
} 

void add_edge2(int u,int v){
	ed[++sum].next=h[u];
	ed[sum].to=v;
	ed[sum].from=u;
	h[u]=sum;
	in[v]++;
} 

int t=0,top=0;
int s[N];
int dfn[N];
int vis[N];
int val[N];
int low[N]; 
int of[N];

void tarjan(int x){
	
	dfn[x]=low[x]=++t;
	s[++top]=x;
	vis[x]=1;
	
	for(int i=head[x];i!=-1;i=edge[i].next){
		int y=edge[i].to;
		
		if(dfn[y]==0){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y]){
			low[x]=min(low[x],dfn[y]);
		}
	}

	
	if(dfn[x]==low[x]){
		int b;
		do{
			b=s[top--];
			of[b]=x;
			vis[b]=0;
			if(b==x){
				break;
			}
			val[x]+=val[b];
		}while(x!=b);
	}
	
}
queue<int> q;
int dis[N];
void topo(){
	
	for(int i=1;i<=n;i++){
		if(of[i]==i&&in[i]==0){
			q.push(i);
			dis[i]=val[i];
		}
	}
	
	while(!q.empty()){
		int k=q.front();
		q.pop();
		for(int i=h[k];i!=-1;i=ed[i].next){
			int v=ed[i].to;
			dis[v]=max(dis[v],dis[k]+val[v]);
			if(--in[v]==0){
				q.push(v);
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		ans=max(ans,dis[i]);
	} 
	cout<<ans;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	n=read(),m=read();
	
	for(int i=1;i<=n;i++){
		head[i]=-1;
		h[i]=-1;
		val[i]=read();
	} 
	
	for(int i=1;i<=m;i++){
		int u,v;
		u=read(),v=read();
		add_edge(u,v);
	}
	
	for(int i=1;i<=n;i++){
		if(dfn[i]==0){
			tarjan(i); 	
		}
	}
	
	for(int i=1;i<=m;i++){
		int x=of[edge[i].from],y=of[edge[i].to];
		if(x!=y){
			add_edge2(x,y);
		}
	}
	
	topo();
	
	return 0; 
}

例题 2.洛谷P3627

例题

就是缩点然后spfa跑找权值最大的终点。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 500005

int n,m;

int uu[N],vv[N];

int cnt=0;
struct Edge{
	int to,next,w;
}edge[2*N];
int head[N];

void add_edge(int u,int v){
	cnt++;
	edge[cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}  
void add_edge2(int u,int v,int w){
	cnt++;
	edge[cnt].to=v;
	edge[cnt].w=w;
	edge[cnt].next=head[u];
	head[u]=cnt;
}  

int t=0,top=0,tot=0;
int s[N];
int dfn[N];
int vis[N];
int val[N];
int sum[N];
int low[N]; 
int of[N];

void tarjan(int x){
	
	dfn[x]=low[x]=++t;
	s[++top]=x;
	vis[x]=1;
	
	for(int i=head[x];i!=-1;i=edge[i].next){
		int y=edge[i].to;
		
		if(dfn[y]==0){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y]){
			low[x]=min(low[x],dfn[y]);
		}
	}

	
	if(dfn[x]==low[x]){
		tot++;
		do{
			int b=s[top];
			sum[tot]+=val[b];
			vis[b]=0;
			of[b]=tot;
		}while(x!=s[top--]);
	}
	
}
queue<int> q;
ll dis[N];
int done[N];
int endd[N];

void spfa(int id){
	
	for(int i=1;i<=tot;i++){
		dis[i]=0;
	}
	
	int gs=of[id];
	q.push(gs);
	done[gs]=1;
	dis[gs]=sum[gs];
	while(!q.empty()){
		int e=q.front();
		q.pop();
		done[e]=0;
		
		for(int i=head[e];i;i=edge[i].next){
			int nx=edge[i].to;
			if(dis[nx]<dis[e]+edge[i].w){
				dis[nx]=dis[e]+edge[i].w;
				if(!done[nx]){
					q.push(nx);
					done[nx]=1;
				}
			}
		}
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	
	for(int i=1;i<=n;i++){
		head[i]=-1;
	} 
	
	for(int i=1;i<=m;i++){
		cin>>uu[i]>>vv[i];
		add_edge(uu[i],vv[i]);
	}
	
	for(int i=1;i<=n;i++){
		cin>>val[i];
	}
	
	int start,ends;
	cin>>start>>ends;
	
	for(int i=1;i<=ends;i++){
		cin>>endd[i];
	}
	
	for(int i=1;i<=n;i++){
		if(dfn[i]==0){
			tarjan(i); 	
		}
	}
	
	cnt=0;
	memset(edge,0,sizeof(edge));
	memset(head,-1,sizeof(head));
	
	for(int i=1;i<=m;i++){
		int x=of[uu[i]],y=of[vv[i]];
		if(x!=y){
			add_edge2(x,y,sum[y]);
		}
	}
	
	spfa(start);
	
	ll ans=0;
	for(int i=1;i<=ends;i++){
		ans=max(ans,dis[of[endd[i]]]);
	} 
	cout<<ans;
	return 0; 
}

标签:tarjan,int,连通,edge,++,dfn,low,分量
From: https://www.cnblogs.com/sadlin/p/18397332

相关文章

  • 信息学奥赛初赛天天练-77-NOIP2015普及组-基础题2-二进制、连通图、最小生成树、链表
    NOIP2015普及组基础题24在计算机内部用来传送、存贮、加工处理的数据或指令都是以()形式进行的A二进制码B八进制码C十进制码D智能拼音码5下列说法正确的是()ACPU的主要任务是执行数据运算和程序控制B存储器具有记忆能力,其中信息任何时候都不会......
  • ensp使用交换机配置svi连通网段
    ensp使用交换机配置svi连通网段实验目的如下图所示,PC1、PC2、PC3分别位于不同网段,使用S5700型号交换机连接,目前需要配置交换机和主机,主机能够互相连通。常用命令uninen:关闭信息通知disipintb:显示端口ip配置情况(brief模式)disiprouting-table:显示路由表vlan<编号>......
  • P8436 【模板】边双连通分量
         ~~~~~     P8436【模板】边双连通分量     ~~~~~......
  • tarjan求LCA
    题面如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。思路这次我们要使用的知识点是\(dfs\)和并查集,这个\(tarjan\)是离线的,我们要先把每个点的每一个要跟它求\(LCA\)的点给记录下来,接下来用\(dfs\)跑这么个流程:遍历这个点的每个子结点并进入子节点将子......
  • Tarjan 之 割点 学习笔记
    首先,要求割点,我们需要知道割点是什么割点:是指在无向连通图中,如果删除某个顶点后,图的连通分量增加,则称该顶点为割点好,知道了这个,那我们怎么去求他呢?Tarjan大神给出了一种依然基于时间戳的算法图片来源:董晓算法割点的求法大概就是这样的所以细节还是见代码吧#include<bit......
  • Tarjan 之 SCC 与 缩点
    这篇文章将讲述作者对Tarjan求SCC与缩点(不是割点)的理解让我们开始吧!TarjanSCC与缩点既然要求\(SCC\)那我们先要弄明白什么是SCCSCC指的是强连通分量强连通指的是若一张有向图的节点两两互相可达,则这张图是强连通的而强连通分量指的是一个极大的连通子图此处的极......
  • 基于VSC的MVDC微电网(±10kV)转换器的互连通过等效RL电缆模块实现,此外,在电缆侧引入了
     ......
  • tarjan之LCA学习笔记
    tarjan之LCA学习笔记tarjan算法求LCA可谓是一个极其巧妙的离线算法其本质是利用DFS遍历时产生的DFS序和并查集来在线性的时间复杂度内求出所有询问的结果既然是离线算法,其和在线算法的区别就在与离线算法需要记录下所有查询,对查询进行一定操作来得到更高的效率,而这......
  • 【Tarjan缩点】USACO5.3 校园网Network of Schools】
    [P2746USACO5.3]校园网NetworkofSchools大意:一个图可能有环a:求deg入度为0的点的个数b:至少加多少条边让图所有点可以互相到达思路:看代码#include<cstdio>#include<queue>#include<deque>#include<stack>#include<map>#include<cmath>#include<algorit......
  • 强连通分量tarjan
    引言强连通分量本质上是处理一个有向有环图(如果在这样的图上搞事情可能会死循环)变成一个有向无环图强连通分量上的点可以互相到达所以针对一些问题(我也没搞明白究竟是哪种问题)例如:给你一张有向图,每个点都有一个点权(不是边权了哦),且每一个点都可以经过任意多次,但是点权只......