区队A 强连通分量

DFS 生成树

首先,以这个有向图为例,我们可以发现其中有4类的边:

\[树边(tree edge):示意图中以黑色边表示,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边.所有相邻的树边组成 DFS 生成树. \\ 反祖边(back edge):也称回边,示意图中以红色边表示(即 7 \rightarrow 1),指在搜索过程中,从某个结点指向其祖先结点的非树边. \\ 前向边(forward edge):示意图中以绿色边表示(即 3 \rightarrow 6),指在搜索过程中,从某个结点指向其子树中后代结点的非树边. \\ 横叉边(cross edge):示意图中以蓝色边表示(即 9 \rightarrow 7),指在搜索过程中,从某个结点指向非祖先、非后代且已访问的结点的边,即不属于上述三类的边. \]

那么考虑这个DFS生成树与强连通分量之间的关系

如果节点\(u\)是某个强连通分量在搜索树中遇到的第一个节点,那么这个强连通分量的剩余节点肯定是在搜索树中以\(u\)为根的子树中

Tarjan 算法

在Tarjan 算法中,我们需要对图进行\(DFS\),我们把每个联通分量看作是搜索树中的一颗子树,并在搜索过程中维护一个栈,每次都把未处理的\(node\)放入栈中

在Tarjan算法中,我们需要维护这几个变量

\[dfn:dfs过程中的序列,即dfs序\\ low:每个节点中可以回溯到的最早在栈中的node \]

当然,我们现在可以发现一些结论:

  1. 一个节点的子树内的dfn都大于该节点的dfn
  2. 从根开始的一条路径上的dfn严格递增,low严格非降

Code

void tarjan(int x)
{
	dfn[x] = low[x] = ++cnt;
	sta[++top] = x;
	in_sta[x]= 1;
	for(int i = 0 ; i < e1[x].size(); i++)
	{
		int y = e1[x][i];
		if(!dfn[y])
		{
			tarjan(y);
			low[x] = min(low[x],low[y]);
		}
		else if(in_sta[y]) //返祖边
		{
			low[x] = min(low[x],dfn[y]);
		}
	}
	if(dfn[x] == low[x])
	{
		++len;
		do
		{
			belong[sta[top]] = len;
			in_sta[sta[top]] = 0;
		}while(sta[top--] != x);
	}
}

B3609 [图论与代数结构 701] 强连通分量

思路

显而易见的,题目就要求你求出所有的强联通分量,而且是在有向图,那么其实就是找环,对于每个点,可以将其编号后处理

Code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
int top, cnt, len;
bool in_sta[N],flag[N];
int dfn[N],low[N],belong[N],sta[N];
vector<int> e[N],scc[N];
void tarjan(int x)
{
	dfn[x] = low[x] = ++cnt;
	sta[++top] = x;
	in_sta[x]= 1;
	for(int i = 0 ; i < e[x].size(); i++)
	{
		int y = e[x][i];
		if(!dfn[y])
		{
			tarjan(y);
			low[x] = min(low[x],low[y]);
		}
		else if(in_sta[y]) //返祖边
		{
			low[x] = min(low[x],dfn[y]);
		}
	}
	if(dfn[x] == low[x])
	{
		++len;
		do
		{
			belong[sta[top]] = len;
			scc[len].push_back(sta[top]);
			in_sta[sta[top]] = 0;
		}while(sta[top--] != x);
	}
}



signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n,m;
	cin>>n>>m;
	for(int i = 1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
	}
	memset(in_sta,0,sizeof(in_sta));
	top = cnt = len = 0;
	for(int i = 1;i <= n ; i ++)
	{
		if(dfn[i] == 0)
		{
			tarjan(i);
		}
	}
	for(int i = 1;i <= len; i ++)
	{
		sort(scc[i].begin(),scc[i].end());
	}
	cout<<len<<'\n';
	memset(flag,0,sizeof(flag));
	for(int i = 1;i <= n;i++)
	{
		if(flag[i]) continue;
		for(int j = 0 ; j < scc[belong[i]].size(); j++)
		{
			int x = scc[belong[i]][j];
            flag[x] = 1;
			cout<<x << ' ';
		}
		cout<<'\n';
	}
	return 0;
}

P3387 【模板】缩点 / 强连通分量

思路

首先,这里说了是有向图,并且重复经过的点全职只计算一次,那么考虑如果是环的情况,一个环就可以看作一个点上值为sum,那么就可以运用\(Tarjan\)进行缩点.

其次,我们需要求的是边权和最大,其实就是最长路,对于最长路,我们可以使用上节课的\(topo\)去求

Code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int top, cnt, len;
bool in_sta[N];
int dfn[N],low[N],belong[N],sta[N];
int a[N],point[N],in_deg[N],que[N],ans[N];
pair<int,int> g[N];
vector<int> e1[N],scc[N],e2[N];
void tarjan(int x)
{
	dfn[x] = low[x] = ++cnt;
	sta[++top] = x;
	in_sta[x]= 1;
	for(int i = 0 ; i < e1[x].size(); i++)
	{
		int y = e1[x][i];
		if(!dfn[y])
		{
			tarjan(y);
			low[x] = min(low[x],low[y]);
		}
		else if(in_sta[y]) //返祖边
		{
			low[x] = min(low[x],dfn[y]);
		}
	}
	if(dfn[x] == low[x])
	{
		++len;
		do
		{
			belong[sta[top]] = len;
//			scc[len].push_back(sta[top]);
			in_sta[sta[top]] = 0;
		}while(sta[top--] != x);
	}
}
void topo()
{
	copy(point+1,point+len+1,ans+1);
	int front = 1,tail = 0;
	for(int i = 1;i <= len; i ++)
	{
		if(in_deg[i]==0)
		{
			que[++tail] = i;
		}
	}
	while(front <= tail)
	{
		int u = que[front++];
		for(int i = 0 ; i < e2[u].size(); i ++)
		{
			int v = e2[u][i];
			--in_deg[v];
			ans[v] = max(ans[v],ans[u] + point[v]);
			if(in_deg[v] == 0)
			{
				que[++tail] = v;
			}
		}
	}
}


signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n,m;
	cin>>n>>m;
	for(int i = 1;i<=n;i++)
	{
		cin>>a[i];
	}
	for(int i = 1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[i] = {u,v};
		e1[u].push_back(v);
	}
	memset(dfn,0,sizeof(dfn));
	memset(in_sta,0,sizeof(in_sta));
	top = cnt = len = 0;
	for(int i = 1;i <= n ; i ++)
	{
		if(dfn[i] == 0)
		{
			tarjan(i);
		}
	}
	memset(point,0,sizeof(point));
	memset(in_deg,0,sizeof(in_deg));
//	cout<<len<<'\n';	
//	memset(flag,0,sizeof(flag));
	for(int i= 1;i <= n;i++)
	{
		point[belong[i]] += a[i];
	}
	for(int i = 1;i<=m;i++)
	{
		int u = belong[g[i].first],v = belong[g[i].second];
		if(u == v)
		{
			continue;
		}
		e2[u].push_back(v);
		++in_deg[v];
	}
	topo();
	int maxx = 0;
	for(int i = 1;i<=len;i++)
	{
		maxx = max(maxx,ans[i]);
	}
	cout<<maxx;
	return 0;
}

接下来的题目的概括

其实可以不难发现,我们对于一个环可以先计算出其奉献后去缩点,如果是有向图的话,我们就相当于是消掉了环,即转换为了\(DAG\),那么就可以使用\(topo\)求解

P.S:\(topo\)的算法复杂度是:\(O(n)\),而\(spfa\)的在最恶劣情况下会是\(Bellman-Ford\)\(O(n \cdot m)\),所以可以转换

posted @ 2026-09-08 13:23  Zhenggeek  阅读(4)  评论(0)    收藏  举报