tarjan提出了不少算法.本文讨论的是图论中求解强连通份量的那个tarjan算法...的应用。c++
讲得不会很是基础,甚至只是起到记录知识的做用.算法
建议先阅读他人的文章,在对tarjan算法有了大概了解后再继续读下去.spa
本文讨论的核心是.net
有向图为什么要缩点code
什么是有向图缩点blog
有向图缩点的实现细节图片
hihoCoder-1185 的实现代码ci
本文暂时(也多是永久)不涉及get
tarjan算法的正确性证实it
tarjan算法的理解
考虑一个有向图,起始点为1,每一个点用正整数编号.给出连通关系以及各节点权值.权值都是天然数.
问: 从1点出发,终点随意,最大路上节点权和能够是多少?
例如: 1->2 就是个合法路径. 这个路径的权值和是6.
如[1]所示.方括号内的数字是此节点的权值.
先考虑暴力算法,DFS.但直接搜下去会死循环.
为何呢?这是由于这个有向图中有环.
3->6
和 6->3
这两条边使得3和6处在一个环内.两个点强连通.
若是不加处理地从1点开始DFS,一定会在3和6之间来回搜索,由于这样路上的节点权和就会无限增加.
那么,把全部的"环"都收缩为一个点,那就成了有向无环图(DAG)了.该DP就DP,该DFS就 DFS,毫无困扰了.
缩点以后是这样的:
这就把6号点"合并"进了3号点.合并以后,3号点和6号点之后等价.
为了方便,咱们用3号点来"表明"6号点和环内其余的点,若是有的话
.
这个思想和并查集中"表明元"的思想很像.而且咱们约定:编号为n
的节点的"表明元"是contract[n]
.若是节点n
不在环中,为了通常性,令contract[n] = n
,即本身的表明元为本身.这与并查集的约定相同.
将一堆点"合并"为一个"表明元"要修改的有两个东西.一个是边,一个是权.
修改边和权,具体实现起来有两种风格.
修改现成的图
从新建图
笔者喜欢修改图.从新建图是弱者的行为(笑).
首先,用tarjan算法找出从1点出发能到达的全部环...固然每次只会找出一个环.咱们说过,每一个环都有且仅有一个"表明元".
接着对于这个环上的每一个非表明元节点,
把它的全部出边复制给"表明元"后删除.
把它的权值加到表明元上.
你可能会想到: 出边所有删除了,那"其余节点"进入环中非表明元节点的边怎么办呢?换言之,非表明元节点的入边怎么解决?
很简单,对于图中的每条入边指向的节点编号k
,都令其等于contract[k]
.
正所谓"有则改之无则加勉"(逃
//AC, 116ms #include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 2e4 + 100; vector<int> e[maxn]; int ins[maxn], dfn[maxn], low[maxn], contract[maxn]; ll w[maxn]; int ind; stack<int> s; void tarjan(int u) { dfn[u] = low[u] = ++ind; ins[u] = 1; s.push(u); for(int i = 0; i < e[u].size(); i++) { int v = e[u][i]; if(!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if(ins[v]) low[u] = min(low[u], dfn[v]); } if(dfn[u] == low[u]) { int v; do { v = s.top(); s.pop(); ins[v] = 0; contract[v] = u; if(u != v) { w[u] += w[v]; while(!e[v].empty()) { e[u].push_back(e[v].back()); e[v].pop_back(); } } } while(u != v); } } ll dfs(int u, ll cnt) { cnt += w[u]; ll re = cnt; for(int i = 0; i < e[u].size(); i++) { int v = contract[e[u][i]]; if(v != u) re = max(re, dfs(v, cnt)); } return re; } int main() { int n, m; cin >> n >> m; for(int i = 1; i <= n; i++) { cin >> w[i]; } for(int i = 1; i <= m; i++) { int u, v; cin >> u >> v; e[u].push_back(v); } tarjan(1); cout << dfs(1, 0) << endl; return 0; }
由于这份代码用的是vector<int>[]
的邻接表存边法,因此效率并非十分高.
各类细节一如前文所述.
之后有空再加一份用链式前向星(咱们一般叫作链表)作邻接表的代码.