首页 > 其他分享 >TARJAN复习 求强连通分量、割点、桥

TARJAN复习 求强连通分量、割点、桥

时间:2023-10-17 22:22:58浏览次数:39  
标签:TARJAN fu int 连通 割点 dfn 求强 分量

TARJAN复习 求强连通分量、割点、桥

目录

感觉之前写的不好,再水一篇博客

强连通分量

“有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

​ ----百度

1188068-20171102114444670-875786699.png (554×310) (cnblogs.com)

像上面的这个图就有三个强连通分量

1-2-3、4、5

设 \(dfn_i\) 记录到达点 \(i\) 的时间戳

设 \(low_i\) 表示 \(i\) 能到达的所有点的时间戳

如果 \(low_i == dfn_i\) 就意味着 \(i\) 和 \(i\) 下面的点能够组成一个强连通分量,因为 \(i\) 下面已经没有边可以往 \(i\) 祖先方向上走了

实现的时候就用一个栈维护一下那个顺序就好了

缩点

P3387 【模板】缩点 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

看一下这个题

对于一个强连通分量来说

我们可以把它缩成一个点,并把这个点的权值设成这个强连通分量里面所有点的权值和。

然后再做 \(dp\) 就好了

#include<bits/stdc++.h>
#define LL long long
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
using namespace std;
stack<int> stk;
queue<int> que;
const int N = 1e4 + 5 , M = 1e5 + 5;
LL ans , f[N] , w[N];
int hd[N] , hd2[N] , num , cnt2 , cnt , p[N] , dfn[N] , low[N] , a[N] , n , ru[N] , m , b[N] , num1;
struct E {
    int nt , to , fr;
}e[M << 1];
struct EE {
    int nt , to;
}e2[M << 1];
int read () {
    int val = 0 , fu = 1;
    char ch = getchar ();
    while (ch < '0' || ch > '9') {
        if (ch == '-') fu = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9') {
        val = val * 10 + (ch - '0');
        ch = getchar ();
    }
    return val * fu;
}
void add (int x , int y) {
    e[++cnt].to = y , e[cnt].nt = hd[x] , e[cnt].fr = x , hd[x] =cnt;
}
void dfs (int x , int fa) {
    dfn[x] = low[x] = ++num;
    stk.push(x);
    int y;
    for (int i = hd[x] ; i ; i = e[i].nt) {
        y = e[i].to;
        if (!dfn[y]) {
            dfs (y , x);
            low[x] = min (low[x] , low[y]);
        }
        else if (!p[y])
            low[x] = min (low[x] , dfn[y]);
    }
    if (low[x] == dfn[x]) {
        y = 0;
        num1 ++;
        while (y != x && !stk.empty()) {
            y = stk.top();
            stk.pop();
            p[y] = num1;
            w[num1] += a[y];
        }
        f[num1] = w[num1]; 
    }
}
void add2 (int x , int y) { e2[++cnt2].to = y , e2[cnt2].nt = hd2[x] , hd2[x] = cnt2; }
void build () {
    int fa1 , fa2 , x , y;
    fu(i , 1 , cnt) {
        x = p[e[i].fr] , y = p[e[i].to];
        if (x == y) continue;
        add2 (x , y);
        ru[y] ++;
    }
}
void tuo () {
    fu(i , 1 , num1)
        if (!ru[i])
            que.push(i);
    int x , y;
    while (!que.empty()) {
        x = que.front();
        que.pop();
        for (int i = hd2[x] ; i ; i = e2[i].nt) {
            y = e2[i].to;
            ru[y] --;
            if (!ru[y])
                que.push(y);
            f[y] = max (f[y] , f[x] + w[y]);
        }
    }
}
int main () {
    int u , v;
    n = read () , m = read ();
    fu(i , 1 , n) 
        a[i] = read ();
    fu(i , 1 , m) {
        u = read () , v = read ();
        add (u , v);
    }
    fu(i , 1 , n)
        if (!dfn[i])
            dfs (i , 0);
    build ();
    tuo ();
    fu(i , 1 , num)
        ans = max (ans , f[i]);
    printf ("%lld" , ans);
    return 0;
}

在一个图中,如果存在一条边,把它删掉,使得整个图被分出来两个互相不连通的图,那么这条边就是桥

\(dfn\) 跟求强连通分量的一样

\(low_i\) 表示 \(i\) 能够到达的最先被访问过的点(不包括 \(i\) 的父亲)

设 \(u , v\) , \(v\) 是 \(u\) 的儿子。

如果 \(low_v > dfn_u\) 就意味着 \(v\) 不能到达 \(u\) 之前的点了,除非经过 \(u\to v\) 这条边,所以这条边就是桥

P1656 炸铁路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++) 
using namespace std;
const int N = 155 , M = 5005;
int n , m , hd[N] , cnt = 1 , dfn[N] , low[N] , num , ans1;
struct E {
    int to , nt;
} e[M << 1];
struct ANS {
    int u , v;
} ans[M];
bool cmp (ANS x , ANS y) { return x.u != y.u ? x.u < y.u : x.v < y.v; }
void add (int x , int y) { e[++cnt].to = y , e[cnt].nt = hd[x] , hd[x] = cnt; }
void dfs (int x , int fa) {
    dfn[x] = low[x] = ++num;
    int y;
    for (int i = hd[x] ; i ; i = e[i].nt) {
        y = e[i].to;
        if (y == fa) continue;
        if (!dfn[y]) {
            dfs (y , x);
            if (dfn[x] < low[y]) {
                ans[++ans1].u = min (x , y);
                ans[ans1].v = max (x , y);
            }
            low[x] = min (low[x] , low[y]);
        }
        else
            low[x] = min (low[x] , dfn[y]);
    }
}
int main () {
    int u , v;
    scanf ("%d%d" , &n , &m);
    fu (i , 1 , m) {
        scanf ("%d%d" , &u , &v);
        add (u , v) , add (v , u);
    }
    fu (i , 1 , n) {
        if (!dfn[i]) 
            dfs (i , 0);
    }
    sort (ans + 1 , ans + ans1 + 1 , cmp);
    fu (i , 1 , ans1) 
        printf ("%d %d\n" , ans[i].u , ans[i].v);
    return 0;
}

割点

在一个图中,如果能够删掉一个点和连接这个点的所有边,使得这个图分成两个不相连的连通块,那么这个点就是割点

跟桥差不多。

因为当你找到一条桥连接 \(u , v\) ,且 \(u\) 是 \(v\) 的父亲时,\(u\) 一定是割点,因为 \(v\) 连不出去了

还有一种情况就是 \(u\) 是根,且 \(u\) 有超过一个不同的子树,那么 \(u\) 也是割点。

P3388 【模板】割点(割顶) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
using namespace std;
const int N = 2e4 + 5 , M = 2e5 + 5;
int n , m , cnt , hd[N] , dfn[N] , low[N] , num , flg[N] , ans;
struct E {
    int to , nt;
} e[M << 1];
void add (int x , int y) { e[++cnt].to = y , e[cnt].nt = hd[x] , hd[x] = cnt; }
void dfs (int x , int fa) {
    dfn[x] = low[x] = ++num;
    int y , sz = 0;
    for (int i = hd[x] ; i ; i = e[i].nt) {
        y = e[i].to;
        if (!dfn[y]) {
            dfs (y , x);
            if (dfn[x] <= low[y] && fa)
                flg[x] = 1;
            low[x] = min (low[x] , low[y]);
            sz ++;
        }
        else
            low[x] = min (low[x] , dfn[y]);
    }
    if (!fa && sz >= 2)
        flg[x] = 1;
    if (flg[x]) ans ++;
}
int main () {
    int u , v;
    scanf ("%d%d" , &n , &m);
    fu (i , 1 , m) {
        scanf ("%d%d" , &u , &v);
        add (u , v) , add (v , u);
    }
    fu (i , 1 , n) {
        if (!dfn[i]) 
            dfs (i , 0);
    }
    printf ("%d\n" , ans);
    fu (i , 1 , n)
        if (flg[i])
            printf ("%d " , i);
    return 0;
}

标签:TARJAN,fu,int,连通,割点,dfn,求强,分量
From: https://www.cnblogs.com/2020fengziyang/p/17770848.html

相关文章

  • 割边+割点 学习心得
    先背诵tarjan板子#include<bits/stdc++.h>using namespace std;#define N 10005#define M 100005int tot,first[N],nxt[M],to[M];void add(int x,int y){    nxt[++tot]=first[x];    first[x]=tot;    to[tot]=y;}int n......
  • 连通性与 Tarjan
    强连通分量和Tarjan强连通分量(StronglyConnectedComponents,SCC),指的是极大的连通子图。tarjan算法,用于求图的强连通分量。dfs生成树有向图的dfs生成树大致分为四种边:树边(treeedge):示意图中以黑色边表示,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边......
  • Tarjan强连通分量详解
    1、简介:在阅读下列内容之前,请务必了解 图论相关概念 中的基础部分。强连通的定义是:有向图G强连通是指,G中任意两个结点连通。强连通分量(StronglyConnectedComponents,SCC)的定义是:极大的强连通子图。这里要介绍的是如何来求强连通分量。2、引入:在介绍该算法之前,先来了解......
  • [学习笔记] Tarjan 连通性全家桶
    拜谢陈老师的PPT!!!无向图割点若点\(x\)不为搜索树的根节点,则\(x\)是割点当且仅当搜索树上存在一个\(x\)的子节点\(y\)满足:\(dfn_x\lelow_y\)。特别地,当\(x\)是搜索树的根节点时,则\(x\)是割点当且仅当有两个点\(y_1,y_2\)满足上述条件。割边边\((x,y)\)是......
  • tarjan学习笔记
    tarjan学习笔记0.前置知识强连通图在一个有向图中,若从任意一点可以到达其他所有点,则称之为强连通图强连通分量(SCC)一个图中的极大强连通性质子图(强连通图的强连通分量是它本身)\(\small{极大强连通子图指一个不能加入另外的点的强连通子图(一个强连通子图可能包含一个或多......
  • 最近公共祖先 Tarjan算法
    P3379【模板】最近公共祖先(LCA)利用并查集点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=5e5+10;vector<int>g[N];vector<pair<int,int>>query[N];intans[N],vis[N];intf[N];intfind(intx){ returnf[x]==x?x:f[x]......
  • Tarjan
    无向图的割点先给出几个定理:A:一棵树中的所有结点对于任意结点的可达性一致。记\(p(u,v)表示u和v可以相互到达\)。也就是说,如果G是一棵树,那么\(\forallu,v\inG,\forallk,p(u,k)\iffp(k,u)\)。B:一个无向图的DFS树中,对于任意一个非树边\((u,v)\),\(u,v\)一定有祖先孩......
  • tarjan 求强连通分量
    for(inti=0;i<n;i++)//图可能是不连通的,因此要表里每个点 if(!dfn[i]) tarjan(i);voidtarjan(intu){ inti,v; dfn[u]=low[u]=ntime++; z.push(u); f[u]=1; for(i=0;i<q[u].size();i++){ v=q[u][i]; if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]);......
  • tarjan强连通分量
    intscc[N],sc;//结点i所在scc的编号intsz[N]; //强连通i的大小//dfn(u)为搜到结点u时的次序编号//low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号//当dfn(u)=low(u)时,以u为根的搜索子树上的所有节点是一个强连通分量voidtarjan(intu){ dfn[u]=low[u]......
  • tarjan求点双连通分量
    边双连通分量见tarjan求边双连通分量部分参考lyd《算法竞赛进阶指南》前置知识给定无向连通图\(G=(V,E)\)割点:若对于\(x\inV\),从图中删去x及其连边,\(G\)分裂成两个及以上不相连子图,那么x是\(G\)的割点时间戳:在深度优先访问时按照每个节点第一次被访问的时间顺......