Tarjan学习笔记

TARJAN

前置知识


  • 强连通:对于两点u, v,从u到v和从v到u都有路径,则称u和v强连通
  • 强联通图:强连通图(Strongly Connected Graph)是指在有向图G中,如果对于任意两点u, v,从u到v和从v到u都有路径,则称G是强连通图
  • 极大强连通子图:一个有向图中无法再加一个点的强连通子图
  • 强连通分量(scc):一个有向图的极大强连通子图
  • 领接表存图
  • dfs

解决的问题


求图的强连通分量
通过缩点将一个有向图转化为一个DAG

思想


通过dfs的过程中搜索顺序和边的特点进行判断点之间的关系(时间戳)

过程


1.一个栈
将dfs搜到的点存在一个栈中

2.两个数组
dfn[ ], low[ ]的定义:
dfn[u]为 dfs 此有向图过程中搜到u的顺序(时间戳) ,  
low[u] 为u在当前栈中能到的点中dfn最小值

3.四种dfs搜索边
树枝边:指向dfs搜索树中还未遍历到的点的边(指向子树中未遍历过得节点)
前向边:指向子树中遍历过得节点的边
后向边:指向栈中祖先的边
横插边:其他边

4.DFS

5.判断low[now]的值
若当前搜到一条树枝边,则用指向点的low值更新
若当前搜到一条前向或后向边,则用指向点的dfn值更新//祖先的low未算好且对答案无影响
若当前搜到一条横插边,则忽略(无影响)

6.求scc(scccnt,sccnum[ ])
若当前节点now的dfn和low值相同,则now为当前求的scc中最深度最小的点(栈中now后的点为scc中的点)
将栈中now节点之后的点出栈,标记为一个scc

7.CODE

int dfn[MAXN], low[MAXN], ind;
int s[MAXN], stp;//栈
int scccnt, sccnum[MAXN], sccsize[MAXN];

void tarjan(int now) {
    dfn[now] = low[now] = ++ind;
    s[stp++] = now;//stp存的是栈顶的上一个位置 
    
    for (int i = head[now]; i ; i = nxt[i]) {
        
        if(!dfn[v[i]]) {
            tarjan(v[i]);
            low[now] = min(low[v[i]], low[now]);//树枝边 
        }
        else if(!sccnum[v[i]]) {
            low[now] = min(dfn[v[i]], low[now]);//前后向边 
        }
        
    } 
    
    
    if(dfn[now] == low[now]) { // 分割点 
        scccnt++;
        while(s[stp] != now) {
            sccnum[s[--stp]] = scccnt;
            sccsize[scccnt]++; 
        } 
    }
    
    return;
}

常见模型

  1. [HAOI2006]受欢迎的牛
//题意为求一个图中唯一的出度为0的强联通 

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100001;
const int MAXM = 50001;
int n, m;

int head[MAXN], cnt, nxt[MAXM], v[MAXM];
void add(int x, int y) {
    nxt[++cnt] = head[x];
    head[x] = cnt;
    v[cnt] = y;
}

int dfn[MAXN], low[MAXN], ind;//dfn[u] : dfs图过程中搜到u的顺序 ,low[u] : u和u可到的子树中dfn的最小值 
int s[MAXN], stp;//栈
int scccnt, sccnum[MAXN], sccsize[MAXN];

int out[MAXN];//每个强联通的出度 
int ans = 0;

void tarjan(int now) {
    dfn[now] = low[now] = ++ind;
    s[stp++] = now;//stp存的是栈顶的上一个位置 
    
    for (int i = head[now]; i ; i = nxt[i]) {
        
        if(!dfn[v[i]]) {
            tarjan(v[i]);
            low[now] = min(low[v[i]], low[now]);//树枝边 
        }
        else if(!sccnum[v[i]]) {
            low[now] = min(dfn[v[i]], low[now]);//前后向边 
        }
        
    } 
    
    
    if(dfn[now] == low[now]) { // 分割点 
        scccnt++;
        while(s[stp] != now) {
            sccnum[s[--stp]] = scccnt;
            sccsize[scccnt]++; 
        } 
    }
    
    return;
}

int main() {
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y);
    }
    
    for (int i = 1; i <= n; i++) {
        if(!dfn[i]) tarjan(i);
    }
    //for (int i = 1; i <= n; i++) printf("dfn[%d] = %d, low[%d] = %d, sccnum[%d] = %d
", i, dfn[i], i, low[i], i, sccnum[i]);
    
    //求唯一的出度为0的强联通 
    for (int i = 1; i <= n; i++) {
        for (int j = head[i]; j ; j = nxt[j]) {
            if(sccnum[i] != sccnum[v[j]])
                out[sccnum[i]]++;
        }
    }
    bool flag = 0;
    for (int i = 1; i <= scccnt; i++) {
        if(flag == 1 && out[i] == 0) {
            printf("0");
            return 0;
        }
        else if(out[i] == 0) {
            ans = sccsize[i];
            flag = 1;
        }
    }
    
    
    printf("%d", ans);
    
    
    return 0;
}
  1. [USACO06JAN]牛的舞会The Cow Prom
//题意为求点数大于1的scc的个数 
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 10001;
const int MAXM = 50001;
int n, m;

int head[MAXN], cnt, nxt[MAXM], v[MAXM];

void add(int x, int y) {
    nxt[++cnt] = head[x];
    head[x] = cnt;
    v[cnt] = y;
}

int s[MAXN], stp;
int dfn[MAXN], low[MAXN], ind;
int sccnum[MAXN], scccnt, sccsize[MAXN];

void tarjan(int now) {
    dfn[now] = low[now] = ++ind;
    s[stp++] = now;
    
    for (int i = head[now]; i ; i = nxt[i]) {
        if(!dfn[v[i]]) {
            tarjan(v[i]);
            low[now] = min(low[now], low[v[i]]);
        }
        else if(!sccnum[v[i]]) {
            low[now] = min(low[now], dfn[v[i]]);
        }
    }
    
    if(dfn[now] == low[now]) {
        scccnt++;
        while(s[stp] != now) {
            sccnum[s[--stp]] = scccnt;
            sccsize[scccnt]++;
        }
    }
}

int main() {
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y);
    }
    
    
    for (int i = 1; i <= n; i++)
        if(!dfn[i]) 
            tarjan(i);
    
    int ans = 0;
    for (int i = 1; i <= scccnt; i++)
        if(sccsize[i] > 1)
            ans++;
    
    printf("%d", ans);
    
    return 0;
}
原文地址:https://www.cnblogs.com/hangzz/p/12384916.html