【暖*墟】 #图论# 强连通分量与缩点(tarjan)

tarjan求有向图强连通分量(scc)

        【一. 常见概念理解】

【二. 常见数组及其使用】

【三. tarjan算法的实现过程】

【四. 常见例题分析】


【一. 常见概念理解】

  • 强连通分量:任意两个节点相互可达的强连通图中最大的一个。
  • tarjan:一个类似dfs的过程,用于寻找有向图中的强连通分量。
  • 缩点:对于具有传导性的关系,把有向图中的"环"缩成点,形成有向无环图。

求强连通分量:先把有向图看成一棵有向树,进行dfs(tarjan)。

当有节点的连边连向dfn比它小的地方时,就形成了环,需要判断连通分量了。


【二. 常见数组及其使用】

(1)、dfn[i],表示时间戳,即这个点在dfs时是第几个被搜到的。

(2)、low[i],表示[这个点以及其子孙节点]连的所有点中[dfn最小的值],

                   即:能匹配到的最上层的祖先的位置。

(3)、stack[i],表示当前过程中可能构成强连通分量的所有点。

(4)、vis[i],表示一个点是否在stack[i]数组中。


【三. tarjan算法的实现过程】

从点u开始遍历(某个dfn为0的节点):

(1)首先初始化:dfn[u]=low[u]=第几个被dfs到。

(2)将u存入stack[]中,并将vis[u]设为true。

--- stack[ ]的意义是:如果u在stack中,在u被回溯时、

u和栈中所有在它之后的点、都可以构成强连通分量。

(3)遍历u的所有连向点v,如果dfn[v]为0,即未访问过,就对点v进行dfs

在遍历子节点的连向边的过程中,如果遍历到dfn比它小的地方

判断该连向节点是否在stack[ ]里,如果在,更新 low[v]。

low[u]=min{low[u],low[v]};

--- low[ u ]的意义是:记录该点能连通到的最大的祖先节点(或者自己),

即能连通到的dfn最小的值,且该值在栈内(保证是u的祖先)。

(4)如果dfn[u]=low[u],说明 [ u点及u点之下的所有子节点 ] 没有边会指向u的祖先,

那么u就是强连通分量的顶端,记录此次搜索到的强连通分量。

把所有的u点及以后压入栈中的所有点弹出,vis[ ]改为false(或打上相同标记)。


【四. 常见例题分析】

       目录  【例题1】【p2341】受欢迎的牛

              【例题2】【p2194】HXY烧情侣

              【例题3】【p2416】泡芙

              【例题4】【p2002】消息扩散

              【例题5】【p2863】牛的舞会

              【例题6】【p1262】间谍网络

              【例题7】【p3043】牛联盟


【例题1】【p2341】受欢迎的牛

  • 告诉你有n头牛,m个崇拜关系,并且崇拜具有传递性,
  • 如果a崇拜b,b崇拜c,则a崇拜c,求最后有几头牛被所有牛崇拜。

【标签】有向图缩点 + 连通性判断 + 入度出度的判断

将整张图(上的强连通分量)进行缩点,剩下的点都不强连通,整张图变成了有向无环图。

有向无环图上必然有>=1个的点出度为0,如果>1个,那么图不相连、答案为0。

如果只有一个点出度为0,判断它是不是强连通分量缩成的点,输出包含的点数。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;

/*【p2341】受欢迎的牛
告诉你有n头牛,m个崇拜关系,并且崇拜具有传递性,
如果a崇拜b,b崇拜c,则a崇拜c,求最后有几头牛被所有牛崇拜。*/

//将整张图(上的强连通分量)进行缩点,剩下的点都不强连通,整张图变成了有向无环图。
//有向无环图上必然有>=1个的点出度为0,如果>1个,那么图不相连、答案为0。
//如果只有一个点出度为0,判断它是不是强连通分量缩成的点,输出包含的点数。

const int N=500019;

ll n,m,x,y,tot=0,head[N*2],tmp=0,ans=0;

ll dfss=0,num=0,sum=0,col[N],times[N],du[N];

ll dfn[N],low[N],stack[N],vis[N]; //tarjan算法中的四个数组

struct node{ ll ver,nextt; }e[N*2];

void add(ll u,ll v){ e[++tot].ver=v,e[tot].nextt=head[u],head[u]=tot; }

void tarjan(ll u){ //dfss记录当前dfs序到达的数字
    
    dfn[u]=low[u]=++dfss,vis[u]=1,stack[++num]=u; //步骤一:初始化
    
    for(int i=head[u];i;i=e[i].nextt){ //步骤二:枚举连向点,递归更新
        if(!dfn[e[i].ver]) tarjan(e[i].ver),low[u]=min(low[u],low[e[i].ver]);
        else if(vis[e[i].ver]) low[u]=min(low[u],dfn[e[i].ver]); //这里写dfn或low都可以
    } //↑↑步骤三:已经到达过,判断是否在当前栈内(栈内都是当前情况下能相连的点)
    
    if(dfn[u]==low[u]){
        col[u]=++sum; vis[u]=0;
        while(stack[num]!=u){ //u上方的节点是可以保留的
            col[stack[num]]=sum;
            vis[stack[num]]=0,num--;
        } num--; //col数组记录每个点所在连通块的编号
    }
}

int main(){
    while(scanf("%lld%lld",&n,&m)!=EOF){
        memset(vis,false,sizeof(vis));
        memset(dfn,0,sizeof(dfn)); 
        dfss=0,num=0,tot=0,sum=0,ans=0,tmp=0;
        memset(head,0,sizeof(head));
        for(ll i=1;i<=m;i++){
            scanf("%lld%lld",&x,&y),add(x,y);
        } for(ll i=1;i<=n;i++)
            if(!dfn[i]) tarjan(i);
        for(ll u=1;u<=n;u++){
            for(ll i=head[u];i;i=e[i].nextt)
                if(col[e[i].ver]!=col[u]) du[col[u]]++;
            times[col[u]]++; //记录强连通分量大小
        } for(ll i=1;i<=sum;i++) //对每种颜色(缩点得到的点)
            if(du[i]==0) tmp++,ans=times[i];
        if(tmp==0||tmp>1) cout<<0<<endl;
        else cout<<ans<<endl;
    }
}

【例题2】【p2194】HXY烧情侣

  • n个点,m条有向边,每个点都有点权w[i]。
  • 如果能从一个点
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值