scc(强连通分量)【tarjon||kosaraju】一点感悟
强连通这个地方看了三天 灵鸡机一动 写一篇博客巩固巩固
首先 tarjan 这个东西貌似是读 [ˈtɑːrjæn](他er见)【也有知乎大佬说好像读[ˈtɑːryæn],按照前面内个读8】
tarjan陪伴强联通分量
生成树完成后思路才闪光
欧拉跑过的七桥古塘
让你 心驰神往
——《膜你抄》
Menci大佬tql %%% 舔舔舔
下面开始心驰神往:
scc(strongly connected components) 有向图强连通分量
DAG ( Directed Acyclic Graph) 有向无环图
为啥要提DAG
因为本菜鸡发现有的题会让你用
Tarjan算法缩点然后再求DAG最长路(DP或spfa求最长路)
也就是Tarjan缩点+DAG最长路||Tarjan缩点+SPFA求DAG上单源最短路
为啥要用缩点
众所周知,有向无环图(DAG)总是有着一些蜜汁优越性,因为没有环,你可以放心的在上面跑dfs,搞DP,但如果是一张有向有环图,事情就会变得尴尬起来了
思考一下会发现如果不打vis标记就会t飞(一直在环里绕啊绕),但是如果打了,又不一定能保证最优解
而你一看题目却发现显然根据一些贪心的原则,这个环上每个点的最大贡献都是整个环的总贡献
这个时候缩点就显得很有必要了,因为单个点的贡献和整个环相同,为什么不去把整个环缩成一个超级点呢?
将有向有环图 通过Tarjan缩点转换为DAG 再搞一搞岂不是美滋滋
比较好的博文
初探tarjan算法 https://www.luogu.org/blog/styx-ferryman/chu-tan-tarjan-suan-fa-qiu-qiang-lian-tong-fen-liang-post
Tarjan算法:详解与心得 https://www.cnblogs.com/yanyiming10243247/p/9294160.html
图的割点与割边 https://www.cnblogs.com/WWHHTT/p/9745499.html
Tarjan算法:求解图的割点与桥(割边) https://www.cnblogs.com/nullzx/p/7968110.html
写这个东西其实就是想把这篇代码贴在这(写的太好了QAQ)然后再给它加点注释
洛谷 P3387(tarjan缩点+求最长路)
注意:
Tarjan+SPFA求DAG上单源最短路模板题
用Tarjan在原图上求SCC 缩点
用缩点之后的SCC建一个有向无环图
SCC权为此SCC内所有点点权和
在新建的DAG上将SCC权视为边权跑SPFA
求SCC[1]到SCC[n]的最长路即为所求答案
原文链接https://blog.csdn.net/yiqzq/article/details/80587578
/* 有向图求缩点 */ #include <bits/stdc++.h> using namespace std; const int maxn = 1e4 + 5; int n, m; struct node { int nxt, v, u; } e[maxn * 10]; int head[maxn], Index, dfn[maxn], low[maxn], scc, top; int belog[maxn], tot, inStack[maxn], Stack[maxn], in[maxn]; //int num[maxn];//各个强连通分量包含点的个数 int value[maxn], vis[maxn], dis[maxn]; vector<int>G[maxn];//临接表存缩点生成的图 int ret[maxn]; void init() { memset(vis, 0, sizeof(vis));//spfa中判断是否已经在队列中 memset(dis, 0, sizeof(dis));//用于求距离 memset(value, 0, sizeof(value));//保存点权 memset(head, -1, sizeof(head));//用于前向星 memset(belog, 0, sizeof(belog));//判断原来的顶点i属于缩点后的的哪个顶点 memset(dfn, 0, sizeof(dfn));//时间戳 memset(low, 0, sizeof(low));//判断顶点所能返回的最早的节点 memset(in, 0, sizeof(in));//判断入度 memset(inStack, 0, sizeof(inStack));//判断是否在栈中 // memset(num, 0, sizeof(num)); memset(ret, 0, sizeof(ret));//缩点后点权的总值 Index = 0;//时间戳 tot = 0;//前向星 top = 0;//模拟栈 scc = 0;//联通分量个数 } void add_edge(int u, int v) {//加边操作 链式前向星存图 e[tot].u = u; e[tot].v = v; e[tot].nxt = head[u]; head[u] = tot++; } void tarjan(int u) { low[u] = dfn[u] = ++Index;//初始化 inStack[u] = 1;//表明u节点已经在栈中 Stack[top++] = u;//将u入栈 for(int i = head[u]; ~i; i = e[i].nxt) { int v = e[i].v; if(!dfn[v]) { tarjan(v);//继续dfs u是起点 v是终点↓ low[u] = min(low[u], low[v]);//u能到达的最小次序号是min(它自己能到达的最小次序号,连接点v能到达的最小次序号) } else if(inStack[v]) { low[u] = min(low[u], dfn[v]);//如果v在栈内,u能到达的最小次序号是min(它自己能到达的最小次序号,v的次序号) } } if(low[u] == dfn[u]) {//如果相等,就说明找到了一个强连通分量 scc++; int v; do { v = Stack[--top]; inStack[v] = 0; belog[v] = scc; // num[scc]++; //统计各个scc包含点的个数 ret[scc] += value[v];//此SCC内所有点点权和为新的ssc点边权 } while(v != u); } } void solve() { for(int i = 1; i <= n; i++ ) { //为了求出所有的连通分量,如果保证是连通图,那么可以不用循环 if(!dfn[i])tarjan(i); } } int spfa(int num) { queue<int>q; q.push(num); dis[num] = ret[num];//这个地方注意一下,初始值dis[x]应该为ret[x] while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = 0; i < G[u].size(); i++ ) { int v = G[u][i]; dis[v] = max(dis[v], dis[u] + ret[v]);//注意这里用spfa跑出来的是最长路 if(!vis[v]) { q.push(v); vis[v] = 1; } } } int ans = 0; for(int i = 1; i <= scc; i++) ans = max(ans, dis[i]); return ans; } int main() { init(); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) scanf("%d", &value[i]); for(int i = 1; i <= m; i++) { int u, v; scanf("%d%d", &u, &v); add_edge(u, v); } solve(); for(int i = 0; i < tot; i++) {//重新构图 int t1 = belog[e[i].u]; int t2 = belog[e[i].v]; if(t1 == t2) continue; G[t1].push_back(t2);//临接表存图 in[t2]++; } int ans = 0; for(int i = 1; i <= scc; i++) { if(!in[i]) {//如果是最大值,那么是以入度为0的节点开始的 //因为这一定是最大的 ,贪心 memset(vis, 0, sizeof(vis)); ans = max(ans, spfa(i)); } } printf("%d\n", ans); return 0; } //5 6 //3 1 //1 2 //2 3 //4 3 //4 5 //5 3
蒟蒻总是更懂你✿✿ヽ(°▽°)ノ✿
kosaraju这个坑改天再来填8(逃)
某个大佬关于tarjan-无向图(桥、割点、双联通分量)的笔记:
一.基本概念
1.桥:对于一个无向图,如果删除某条边后,该图的连通分量增加,则称这条边为桥
2.割点/割项:对于一个无向图,如果删除某个节点u节点后,该图的连通分量增加,则节点u为割项或关节点
3.点-双联通:对于一个连通图,如果任意两点至少存在两条点不重复路径,则称这个图为点双连通(也就是通常说的的双联通)
4.边-双联通:对于一个连通图,如果任意两点至少存在两条边不重复路径,则称该图为边双连通的
二.判断条件
1.桥:
- 存在重边必定不为桥
-low[v]>dfn[u]
2.割点/割项:
-u不为搜索起点,low[v]>=dfn[u]
-u为搜索起点,size[ch]>=2
PS: –> 一般情况下,不建议在tarjan中直接输出答案(可能会有重复)
–> 在有重边的情况下,将tarjan传值中的father改为其编号,由于存边的连续性 只要判断 (当前编号)i != (father编号)pre^1
{ 初始值tarjan(i,-1) 编号:0—-2m }
*************************************************
3.点-双联通:大致同求割点
-用栈保存边(并非点)
-遇到割点后开始出栈,直到当前的边出栈后
4.边-双联通:(目前只提供一种方法 其实是不知道另一种方法)
-第一次dfs找出所有的桥
-第二次dfs保证不经过桥.一个连通图即为一个边-双联通分量
三.构造双连通图
缩点后找度为1的个数,需要加的边为(tot+1)/2
---------------------
原文:https://blog.csdn.net/qq_27121257/article/details/78083791
点&&边双连通看这篇博文:
寒假2019培训:双连通分量(点双+边双) https://blog.csdn.net/hailiang70303/article/details/87553759
写的比较乱orz