强连通分量(缩点)学习笔记 (updating)
作者:互联网
前言
由于本人的文化课炸掉了,所以说从此用心开始学计算机,再也不插科打诨了。
概念
在一个有向联通图中,如果说有若干个点之间成一个环(即任意两点之间可以互相到达),那么我们称这些点为一个强连通分量,下文统一用 \(scc\) 来表示。
意义
在对图论问题的分析中,其可以进行缩点,割边等优秀操作来节省操作难度。
模板题
讲解
对于一个有向无权图,如果说多个点成了一个环,那么我们的操作就开始十分的复杂了,所以说我们需要用强连通分量算法 tarjan 来优化我们的算法。
对于 tarjan 这个位老哥我不做多的介绍,感兴趣的可以上网搜一搜他的个人简介。
步入正题
tarjan 算法的主要思想在于找一个环,如果说找到了环,那么就一定可以确定这几个点是一个强连通分量。
那么怎么找环和怎么将一个环缩成一个点就成了我们需要注意的最大问题。
此时我们引入一个概念:时间戳。
用两个变量来定义时间戳。
\(dfn\) : 表示这个点是第几个被访问到的。
\(low\) : 表示这个点目前所在的强连通分量中能够到达的最上面的时间戳。
有了以上两个概念我们就可以开始写强连通分量了(bushi)。
其实我们还需要思考一个问题,如何将这些点真正的缩成一个点。既然都说了是缩成一个点,那么我们创建一个点不就完事了吗。
下面的讲解以及代码中将会用 \(scc\) 来表示我所创建的点。
我们考虑将已经便利过的点存在一个栈中,在回溯的时候我们只需要不断地弹栈这样就可以做到缩点了。
以上步骤全都包含在下列代码中,如果说有什么不懂的话,我建议去看这位大佬的报告,写的十分详细和有趣。
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int maxn = 1e4+1;
vector<int>e[maxn],e2[maxn];
int stk[maxn],tp,dfn[maxn],low[maxn],ccnt,scc[maxn],cnt,w1[maxn],w2[maxn],n,m,ans;
bool vis[maxn];
int f[maxn];
void tarjan(const int o) {
dfn[o] = low[o] = ++cnt;
stk[++tp] = o,vis[o] = true;
for(auto x:e[o]) {
if(!dfn[x]) {
tarjan(x);
low[o] = min(low[o],low[x]);
} else if(vis[x]) {
low[o] = min(low[o],dfn[x]);
}
}
if(dfn[o] == low[o]) {
int y ;
ccnt++;
while(y = stk[tp--]) {
scc[y] = ccnt;
vis[y] = false;
w2[ccnt] += w1[y];
if(o == y) break;
}
}
return ;
}
int dfs(const int x) {
if(vis[x]) return f[x];
vis[x] = true;
for(auto t:e2[x]) {
f[x] = max(f[x],dfs(t));
}
return f[x] += w2[x];
}
int main() {
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++) scanf("%d",&w1[i]);
for(int i = 1,u,v; i <= m; i++) scanf("%d%d",&u,&v),e[u].push_back(v);
for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i);
for(int i = 1; i <= n; i++) {
for(auto x:e[i]) {
if(scc[i] != scc[x]) {
e2[scc[i]].push_back(scc[x]);
}
}
}
memset(vis,0,sizeof(vis));
for(int i = 1; i <= n; i++) {
if(!vis[i]) ans = max(ans,dfs(i));
}
printf("%d\n",ans);
return 0;
}
以上
标签:缩点,连通,int,tarjan,vis,dfn,maxn,low,updating 来源: https://www.cnblogs.com/woody-wong/p/14358579.html