强连通分量算法·$tarjan$初探

Posted pks-t

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了强连通分量算法·$tarjan$初探相关的知识,希望对你有一定的参考价值。

嗯,今天好不容易把鸽了好久的缩点给弄完了……感觉好像……很简单?

算法的目的,其实就是在有向图上,把一个强连通分量缩成一个点……然后我们再对此搞搞事情,(over)

哦对,时间复杂度很显然是(Theta(n))的,懒得(Proof)了。

真是简明扼要的算法啊(233)

比较弱智的代码是下面的:

#include <stack>
#include <cstdio>
#include <iostream>
#define min Min
#define max Max
#define MAXN 10010
#define MAXM 50010
#define to(k) E[k].to

std::stack <int> S ;
struct Edge{
    int to, next ;
}E[MAXM] ; int head[MAXN], vis[MAXN], c ;
int N, M, A, B, Ans, dfn[MAXN], low[MAXN], cnt ;

inline int Min(int a, int b) { return a & ((a - b) >> 31) | b & (~(a - b) >> 31) ; }
inline int Max(int a, int b) { return a & ((b - a) >> 31) | b & (~(b - a) >> 31) ; }
inline void _Add(int u, int v){ E[++ cnt].to = v, E[cnt].next = head[u], head[u] = cnt ;}
void Tarjan(int u){
    S.push(u), vis[u] = 1 ;
    dfn[u] = low[u] = ++ c ;
    for (int k = head[u] ; k ; k = E[k].next){
        if (vis[to(k)]) low[u] = min(low[u], low[to(k)]) ;
        else if (!dfn[to(k)]) Tarjan(to(k)), low[u] = min(low[u], low[to(k)]) ;
    }
    if (dfn[u] == low[u]) ++ Ans ;
}
int main(){
    int i ; std::cin >> N >> M ;
    for (i = 1 ; i <= M ; ++ i) scanf("%d%d", &A, &B), _Add(A, B) ;
    for (i = 1 ; i <= N ; ++ i) if (!dfn[i]) Tarjan(i) ; printf("%d", Ans) ; return 0 ;
}

十分(zz)的统计联通块个数……当然还有进阶版本:

(mathcal{Description})

(Link)

(mathcal{Solution})

其实就是让求大小非(1)的联通块个数……稍微弹个栈就行了(233)

#include <stack>
#include <cstdio>
#include <iostream>
#define min Min
#define max Max
#define MAXN 10010
#define MAXM 50010
#define to(k) E[k].to

std::stack <int> S ;
struct Edge{
    int to, next ;
}E[MAXM] ; int head[MAXN], vis[MAXN], c ;
int N, M, A, B, Ans, dfn[MAXN], low[MAXN], cnt ;

inline int Min(int a, int b) { return a & ((a - b) >> 31) | b & (~(a - b) >> 31) ; }
inline int Max(int a, int b) { return a & ((b - a) >> 31) | b & (~(b - a) >> 31) ; }
inline void _Add(int u, int v){ E[++ cnt].to = v, E[cnt].next = head[u], head[u] = cnt ;}
void Tarjan(int u){
    S.push(u), vis[u] = 1 ;
    dfn[u] = low[u] = ++ c ;
    for (int k = head[u] ; k ; k = E[k].next){
        if (vis[to(k)]) low[u] = min(low[u], low[to(k)]) ;
        else if (!dfn[to(k)]) Tarjan(to(k)), low[u] = min(low[u], low[to(k)]) ;
    }
    if (dfn[u] == low[u]){
        int t = 0 ;
        while(!S.empty()){
            int T = S.top() ;
            ++ t ; S.pop() ;
            if (T == u) break ;
        }
        Ans += (t > 1) ;
    }
}
int main(){
    int i ; std::cin >> N >> M ;
    for (i = 1 ; i <= M ; ++ i) scanf("%d%d", &A, &B), _Add(A, B) ;
    for (i = 1 ; i <= N ; ++ i) if (!dfn[i]) Tarjan(i) ; printf("%d", Ans) ; return 0 ;
}

还有更加进阶的版本:

(mathcal{Description})

(Link)

(mathcal{Solution})

就是缩完点之后跑(DP)……[DP ~ in ~Graph= Floyd = ext{最短路} = SPFA]这个题里,这个思路好像没问题……

那么就直接缩完点在联通块之间(SPFA)就行。

#include <stack>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#define max Max
#define MAX 100010
#define to(k) E[k].to

using namespace std ;
stack <int> S ;
queue <int> q ;
struct Edge{
    int to, next, v ;
}E[MAX] ; int A, B, N, M, Ans, tot, cnt, c ;
int head[MAX], dist[MAX], Edges[MAX][2], val[MAX] ;
int base[MAX], vis[MAX], clr[MAX], dfn[MAX], low[MAX] ;

inline void Tarjan(int now){
    S.push(now), vis[now] = 1, 
    low[now] = dfn[now] = ++ c ;/**/
    for (int k = head[now] ; k ; k = E[k].next){
        if(vis[to(k)]) low[now] = min(low[now], dfn[to(k)]) ;
        else if (!dfn[to(k)]) Tarjan(to(k)), low[now] = min(low[now], low[to(k)]) ;
    }
    if (dfn[now] == low[now]){
        ++ tot ;
        while(!S.empty()){
            int t = S.top() ;
            clr[t] = tot, vis[t] = 0, 
            val[tot] += base[t], S.pop() ;
            if (t == now) break ;
        }
    }
}
inline int Max(int a, int b){ return a & ((b - a) >> 31) | b & (~(b - a) >> 31) ; }
inline void Clear(){cnt = 0, fill(head, head + N + 3, 0) ; memset(E, 0, sizeof(E)) ;}
inline void _Add(int u, int v){ E[++ cnt].to = v, E[cnt].next = head[u], head[u] = cnt ;}
inline void SPFA(int x){
    fill(vis, vis + N + 2, 0),
    fill(dist, dist + N + 2, 0) ;  
    dist[x] = val[x], vis[x] = 1, q.push(x) ;
    while (!q.empty()){
        int now = q.front() ; q.pop(), vis[now] = 0 ;
        for (int k = head[now] ; k ; k = E[k].next){
            int v = E[k].to ; 
            if (dist[v] < dist[now] + val[v]) {
                dist[v] = dist[now] + val[v] ;
                if (!vis[v]) vis[v] = 1, q.push(v) ;
            }
        }
    }
    for (int i = 1 ; i <= tot ; ++ i) Ans = max(Ans, dist[i]) ;
}
int main(){
    int i ; cin >> N >> M ; 
    for (i = 1 ; i <= N ; ++ i) scanf("%d", &base[i]) ;
    for (i = 1 ; i <= M ; ++ i) 
        Edges[i][0] = A, Edges[i][1] = B, scanf("%d%d", &A, &B), _Add(A, B) ; 
    /**/for (i = 1 ; i <= N ; ++ i) if (!dfn[i]) Tarjan(i) ; Clear() ;
    for (i = 1 ; i <= M ; ++ i) if (clr[Edges[i][0]] != clr[Edges[i][1]]) _Add(clr[Edges[i][0]], clr[Edges[i][1]]) ;/**/
    for (i = 1 ; i <= tot ; ++ i) SPFA(i) ; printf("%d
", Ans) ; return 0 ;
}

个人觉得缩点……没啥好说的……因为比较简单嘛……

以上是关于强连通分量算法·$tarjan$初探的主要内容,如果未能解决你的问题,请参考以下文章

HDU 1269 迷宫城堡 tarjan算法求强连通分量

图之强连通强连通图强连通分量 Tarjan算法

强连通分量——tarjan算法

hihoCoder#1185 : 连通性·三 tarjan求强联通分量 缩点 dfs/拓扑排序求路径和最大值

有向图强连通分量的Tarjan算法

图论-强连通分量-Tarjan算法