hdu-2767-Tarjan求强联通+缩点

本文介绍强连通分量的概念及其在有向图中的应用,并详细讲解Tarjan算法实现过程,通过实例说明如何利用该算法解决实际问题。

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

先讲一些知识点(强联通都知道就不说了)

根据这个还有这个总结得极大极小连通子图(貌似有的离散课本讲了):

极大的就是本身,极小就是包含连通图中所有顶点,但是在多加一条边就会产生环,减少一条边就无法构成连通,也就是生成树

无向连通图的极大连通子图是其本身(唯一),极小连通子图是其生成树(不唯一)

无向不连通图的极大连通子图就是各个连通(不唯一),极小连通子图是各个连通的生成树(不唯一)

有向连通图的极大连通子图为其本身(唯一)

有向不连通图的极大连通子图为各个连通(不唯一)

有向图不存在极小连通子图的概念。

关于Tarjan的过程看这个

然后再来说这道题:

首先可以用Tarjan算法求出强联通的个数,然后压缩这个图,即每个强联通压缩成一个点(这叫缩点),完成后新图是一个DGA,那么只要找出最少的边加入这个新的DAG使得强联通个数为1就行了。

那么如何找出需要加的边的个数呢?

首先遍历所有的边,如果这边连接两个强联通,那么就记录下这条边,完成后检查每个强联通的入度和出度,分别记录下入度为0和出度为0的点的个数,那么最少的边数就是两者的最大值。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>

using namespace std;

const int N=20000+5;
int n,m,t,x,y;
vector<int> G[N];
int pre[N],lowlink[N],sccno[N],in[N],out[N];
int dfs_clock,scc_cnt;
stack<int> S;

void DFS(int u)
{
    //pre表示u的搜索次序编号
    //lowlink表示u和u的子树能追溯到的最早的节点的次序编号
    pre[u]=lowlink[u]=++dfs_clock;//每次记录当前节点的次序编号,并且当前最早的能追溯到的初始化为自己的编号,也可以说一开始把每个点都看做一个强联通
    S.push(u);
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])//如果没有搜索过
        {
            DFS(v);
            lowlink[u]=min(lowlink[u],lowlink[v]);//更新能追溯到的祖先的次序编号
        }
        else if(!sccno[v])//如果搜索过
            lowlink[u]=min(lowlink[u],pre[v]);
    }
    if(lowlink[u]==pre[u])//找到当前的强连通中祖先编号最小的是自己的编号(也就是又到了当前这个强联通的起点),表明当前的强联通寻找完成
    {
        scc_cnt++;
        while(1)
        {
            int x=S.top();
            S.pop();
            sccno[x]=scc_cnt;
            if(x==u) break;
        }
    }
}

void ini(int n)
{
    for(int i=0;i<=n;i++) G[i].clear();
    dfs_clock=scc_cnt=0;
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
    memset(pre,0,sizeof(pre));
    memset(sccno,0,sizeof(sccno));
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        ini(n);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            G[x].push_back(y);
        }
        for(int i=1;i<=n;i++) if(!pre[i]) DFS(i);
        if(scc_cnt==1) printf("%d\n",0);
        else
        {
            for(int u=1;u<=n;u++)//缩点
            {
                for(int j=0;j<G[u].size();j++)
                {
                    int v=G[u][j];
                    if(sccno[u]!=sccno[v])//如果边u->v正好连接两个强联通那就记录下来已经存在的边
                    {
                        out[sccno[u]]++;
                        in[sccno[v]]++;
                    }
                }
            }
            int innum=0,outnum=0;
            for(int i=1;i<=scc_cnt;i++)
            {
                if(in[i]==0) innum++;
                if(out[i]==0) outnum++;
            }
            printf("%d\n",max(innum,outnum));
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值