Tarjan算法、缩点染色及最小权点基

本文深入解析Tarjan算法在有向图中的应用,详细介绍了如何通过Tarjan算法解决强连通分量问题,以及如何利用强连通分量解决实际问题,如最少顶点选择和最少边添加。同时,提供了完整的代码示例和测试用例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看了许多博客,还是这个最清楚:强连通算法–Tarjan个人理解+详解
墙裂安利大家看看上面的博客!讲的非常清楚!(但是文章的代码好像有一点点小错误。
Tarjan算法主要解决了有向图中有几个强连通分量的问题,基于Tarjan算法,可以对在同一连通分量的点染色,进而缩点,生成有向无环图。
例:给定有向图,求:

  1. 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点
  2. 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点
    思路 缩点之后, DAG上面有多少个入度为0的顶点,问题1的答案就是多少;在DAG上要加几条边,才能使得DAG变成强连通的,问题2的答案就是多少。
    加边的方法:

假定有 n 个入度为0的点,m个出度为0的点,如何加边?
把所有入度为0的点编号 0,1,2,3,4 …N -1
每次为一个编号为i的入度0点可达的出度0点,添加一条出边,连到编号为(i+1)%N 的那个入度0点,这需要加n条边。
若 m <= n,则加了这n条边后,已经没有入度0点,则问题解决,一共加了n条边
若 m > n,则还有m-n个入度0点,则从这些点以外任取一点,和这些点都连上边,即可,这还需加m-n条边。
所以,max(m,n)就是第二个问题的解

附上代码:

#include<stdio.h>//代码仅供参考
#include<string.h>
#include<vector>
#include<algorithm>
#include<stack>
using namespace std;
#define maxn 1000000
struct E{
    int next,v;
}edge[maxn];
int head[maxn],k;
int vis[maxn];
int DFN[maxn],LOW[maxn],cnt,sig,color[maxn];
int in_cnt,out_cnt,in[maxn],out[maxn];
int n,m;
stack<int>S;
void init(){
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(DFN,0,sizeof(DFN));
    memset(LOW,0,sizeof(DFN));
    memset(color,0,sizeof(color));
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
    while(!S.empty())S.pop();
    k = 0,cnt = 0,sig = 0;
}
void add(int u,int v){//链式前向星
    edge[++k].next = head[u];
    edge[k].v = v;
    head[u] = k;
}
void Tarjan(int u){
    DFN[u] = LOW[u] = ++cnt;
    vis[u] = 1;
    S.push(u);
    for(int i = head[u];i != -1;i = edge[i].next){
        int v = edge[i].v;
        if(!DFN[v]){
            Tarjan(v);
            LOW[u] = min(LOW[u],LOW[v]);
        }
        else if(vis[v] == 1){
            LOW[u] = min(LOW[u],LOW[v]);
        }
    }
    if(DFN[u] == LOW[u]){
        sig++;
        do{
            int x = S.top();
            color[x] = sig;
            S.pop();
            vis[x] = -1;
            printf("%d",x);
            if(x == u)break;
        }while(1);
        printf("\n");
    }
}

void Slove(){
    for(int i = 1; i <= n;++i){
        if(vis[i] == 0)Tarjan(i);
    }
    printf("强连通分量共%d个\n",sig);
}

//1) 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点

//2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点
void scc(){
    if(sig == 1){
        printf("%d %d\n",1,0);
    }
    else{
        for(int i = 1; i <= n; ++i){
        for(int j = head[i]; j != -1;j = edge[j].next){
            int v = edge[j].v;
            if(color[i] != color[v]){
                in[color[v]]++;
                out[color[i]]++;
            }
        }
    }
    for(int i = 1; i <= sig;++i){
        if(!in[i])in_cnt++;
        if((!out[i]))out_cnt++;
    }

    printf("至少要选 %d 个顶点,才能从这些顶点出发,可达全部顶点\n至少要加 %d 条边,使得从任何一个顶点出发,到达全部顶点\n",in_cnt,max(in_cnt,out_cnt));
    }

}
int main()
{
    while(~scanf("%d",&n)){
        scanf("%d",&m);
        init();
        for(int i = 1; i <= m;++i){
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,v);
        }
        Slove();
        for(int i = 1; i <= n;++i){
            printf("颜色%d,结点%d\n",color[i],i);
        }
        scc();
    }
}


一组测试用例
在这里插入图片描述

相关练习:
POJ2186找大牛
POJ1236

强连通图缩点的应用:最小点基

题目大意:

给你N个炸弹,对应已知其坐标和爆炸范围,以及引爆这个炸弹需要的花费,对应如果引爆了炸弹a,没有引爆炸弹b,但是b炸弹在a炸弹的作用范围之内,那么b炸弹也会被引爆,问将所有炸弹都引爆需要的最小花费。
思路:把可以引爆的炸弹间连边,建立有向图;如果有环,使用Tarjan算法缩点染色(同环内可以相互引爆);找到入度为0的分量,找出其中花费最小的点作为引爆这个分量及其相连分量的点累加。
综上所述,Tarjan强连通缩点染色之后,找到度为0的节点,并且在其中找到花费最小的炸弹,累加即可。
具体的:在DFN[u] == LOW[v]时更新分量的最小花费

关于最小点基和最小权点基:最小点基,最小权点基(Summer Holiday HDU - 1827)(强连通分量的应用)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值