Tarjan:强连通分量 割点

(他羊)

Tarjan是一种dfs算法。

有向图的强联通分量

如果一个有向图的子图中,任意两点可以相互到达,那么这就组成了一个强联通分量。

在Tarjan算法中,需要维护一个栈stk,

每个节点有两个值:dfn[],即dfs序;low[],表示这个节点最多经过一条横叉边能到达的dfn最小的点的dfn。

流程:

dfs到当前的节点为u,枚举u能到达的点v。

若v没有被dfs过,递归dfs,并用low[v]更新low[u];

若v被更新过且在栈中,则说明u->v是一条横叉边,并用dfn[v]更新low[u]。

遍历完u的子节点后,若dfn[u] == low[u],则说明构成了一个强联通分量。将栈中u及以后的节点都染成相同的颜色并弹出。

  • 各个强联通分量出栈的顺序是缩点形成的图中拓扑序的逆序。

 

几道模板题:

Luogu P3387 【模板】缩点 缩点+记忆化搜索

 

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#define MogeKo qwq
using namespace std;
const int maxn = 2e5+10;
int n,m,ans;
int cnt,num,now,top;
int a[maxn],x[maxn],y[maxn],val[maxn],f[maxn];
int dfn[maxn],low[maxn],sta[maxn],col[maxn];
int head[maxn],to[maxn],nxt[maxn];
bool insta[maxn];

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

void clear() {
    memset(head,0,sizeof(head));
    memset(to,0,sizeof(to));
    memset(nxt,0,sizeof(nxt));
    cnt = 0;
}

void tarjan(int u) {
    dfn[u] = low[u] = ++now;
    sta[++top] = u;
    insta[u] = true;
    for(int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if(!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u],low[v]);
        } else if(insta[v])
            low[u] = min(low[u],dfn[v]);
    }
    if(low[u] == dfn[u]) {
        col[u] = ++num;
        insta[u] = false;
        val[num] += a[u];
        while(sta[top] != u) {
            int v = sta[top--];
            col[v] = num;
            insta[v] = false;
            val[num] += a[v];
        }
        top--;
    }
}

void dfs(int u) {
    if(f[u]) return;
    f[u] = val[u];
    int sum = 0;
    for(int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        dfs(v);
        sum = max(sum,f[v]);
    }
    f[u] += sum;
}

int main() {
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++)
        scanf("%d",&a[i]);
    for(int i = 1; i <= m; i++) {
        scanf("%d%d",&x[i],&y[i]);
        add(x[i],y[i]);
    }
    for(int i = 1; i <= n; i++)
        if(!dfn[i]) tarjan(i);
    clear();
    for(int i = 1; i <= m; i++)
        if(col[x[i]] != col[y[i]])
            add(col[x[i]],col[y[i]]);
    for(int i = 1; i <= num; i++) {
        dfs(i);
        ans = max(ans,f[i]);
    }
    printf("%d",ans);
    return 0;
}
View Code

Luogu P2341 [HAOI2006]受欢迎的牛 

#include<cstdio>
#include<iostream>
#define MogeKo qwq
using namespace std;

const int maxn = 100005;
int n,m,a,b,sum,ans;
int now,top,num;
int dfn[maxn],low[maxn],sta[maxn],out[maxn],col[maxn];
int cnt,to[maxn],nxt[maxn],head[maxn];
bool insta[maxn];

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

void Tarjan(int u) {
    dfn[u] = low[u] = ++now;
    sta[++top] = u;
    insta[u] = true;
    for(int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if(!dfn[v]) {
            Tarjan(v);
            low[u] = min(low[u],low[v]);
        } else if(insta[v])
            low[u] = min(low[u],dfn[v]);
    }
    if(low[u] == dfn[u]) {
        col[u] = ++num;
        insta[u] = false;
        while(sta[top]!=u) {
            int v = sta[top];
            col[v] = num;
            insta[v] = false;
            top--;
        }
        top--;
    }
}

int main() {
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= m; i++) {
        scanf("%d%d",&a,&b);
        add(a,b);
    }
    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            Tarjan(i);
    for(int u = 1; u <= n; u++)
        for(int i = head[u]; i; i = nxt[i]) {
            int v = to[i];
            if(col[u] != col[v])
                out[col[u]]++;
        }
    for(int i = 1; i <= num; i++) {
        if(!out[i]) {
            if(ans) {
                printf("0");
                return 0;
            }
            for(int u = 1; u <= n; u++)
                if(col[u] == i)ans++;
        }
    }
    printf("%d",ans);
    return 0;
}
View Code

 

无向图的强连通分量

割点:删去后原图不连通的点。

割边(桥):删去后原图不连通的边。

点双连通分量:无割点的极大强连通子图。

边双连通分量:无割边的极大强连通子图。

  • 有割点不一定有桥,有桥一定存在割点。
  • 桥一定是割点依附的边。
  • 一个点可以在最多2个点双里,但只能在1个边双里(否则可以合并)。

求割点/割边:

根结点u为割顶当且仅当它有两个或者多个子结点; 
非根结点u为割顶当且仅当u存在结点v,使得v极其所有后代都没有反向边可以连回u的祖先(不包括u),即low[v]>=dfn[u]。

桥的求法其实也是类似的,当结点u的子结点v的后代通过反向边只能连回v,那么删除这条边(u, v)就可以使得图非连通了。即low[v]>dfn[u]。

Luogu P3388 【模板】割点(割顶)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#define MogeKo qwq
using namespace std;
const int maxn = 1e6+10;
const int INF = 2147483647;
int n,m,cnt,now,num,top,sum;
int x,y,z;
int to[maxn],head[maxn],nxt[maxn];
int dfn[maxn],low[maxn];
bool iscut[maxn];

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

void Tarjan(int u,int fa) {
    dfn[u] = low[u] = ++now;
    int ch = 0;
    for(int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if(!dfn[v]) {
            if(fa == u)ch++;
            Tarjan(v,fa);
            low[u] = min(low[u],low[v]);
            if(fa != u && low[v] >= dfn[u]) iscut[u] = true;
        }
        low[u] = min(low[u],dfn[v]);
        if(fa == u && ch >= 2) iscut[u] = true;
    }
}

int main() {
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= m; i++) {
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            Tarjan(i,i);
    for(int i = 1; i <= n; i++)
        if(iscut[i])sum++;
    printf("%d\n",sum);
    for(int i = 1; i <= n; i++)
        if(iscut[i]) printf("%d ",i);
    return 0;
}
View Code

 

圆方树

将每个点双作为一个方点,这个点双中的点作为圆点,去掉点双中所有内部的边,并将圆点连接到对应的方点。

 

 

转载于:https://www.cnblogs.com/mogeko/p/10809007.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值