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

posted @ 2019-08-02 16:40  观稳769  阅读(489)  评论(0编辑  收藏  举报