强连通分量、割边、割点、双连通分量学习小结

To be continued…

强连通分量

在有向图G中,如果任意两个不同的顶点相互可达,则称该有向图是强连通的。

Korasaju算法求有向图强连通分量

现在才知道叫这个名字
1.深度优先遍历G,算出每个结点u的结束时间f[u],起
点如何选择无所谓。
2.将G中所有边反向,选择深度优先遍历的起点时,按照结点的结束时间从大到小进行。(用个栈就可以了)。同时进行标记
3. 第2步中产生的标记值相同的结点构成深度优先森林中的一棵树,也即一个强连通分量

bool vis[N];
int scc[N],sccc;
stack<int> S;
void dfs1(int u){
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(edge[i].ban||vis[v])
            continue ;
        dfs1(v);
    }
    S.push(u);
}
void dfs2(int u,int num){
    scc[u]=num;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(edge[i].ban||scc[v])
            continue;
        dfs2(v,num);
    }
}
void find_scc(){
    for(int i=0;i<n;i++){
        if(!vis[i])
            dfs1(i);
    }
    for(int i=1;i<=cnt;i+=2){
        edge[i].ban=1;
        edge[i+1].ban=0;//反向
    }
    while(!S.empty()){
        int x=S.top();
        S.pop();
        if(scc[x])
            continue ;
        sccc++;
        dfs2(x,sccc);
    }
}

Tarjan算法求强连通分量

先来几个定义
dfn为dfs序(即dfs访问顺序)
low[i]表示从i节点出发DFS过程中i下方节点(开始时间大于dfn[i],且由i可达的节点)所能到达的最早的节点的开始时间。
显然,low[i]<=dfn[i]
具体实现感觉很难用文字表述,直接上代码吧

void dfs(int u){
    low[u]=dfn[u]=++tot;
    instack[u]=1;
    S.push(u);
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else
            if(instack[v])
                low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        scccnt++;
        while(1){
            int v=S.top();
            S.pop();
            scc[v]=scccnt;
            sz[scccnt]++;
            instack[v]=0;
            if(u==v)
                break;
        }
    }
}

割边、割点

无向连通图中,如果删除某点后,图变成不连通,则称该点为割点。
无向连通图中,如果删除某边后,图变成不连通,则称该边为割边(又称桥)。
先构建dfs树,dfs树中的父子关系边称为树边
一个顶点u是割点,当且仅当满足(1)或(2)
(1) u为树根,且u有多个直接儿子。
(2) u不为树根,且存在(u,v)为树边,使得

dfn(u)<=low(v) d f n ( u ) <= l o w ( v )

一条边(u,v)是割边,当且仅当(u,v)为树边,且满足
dfn(u)<low(v) d f n ( u ) < l o w ( v )

//本算法适用于无重边
//ans1为割点数量,ans2为割边数量
void dfs(int u,int fa){
    bool k=1;//避免重复计算割点
    low[u]=dfn[u]=++tot;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            dfs(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u]){
                if(dfn[u]==1)
                    rootsum++;
                else if(k){
                    ans1++;
                    k=0;
                }
                if(low[v]>dfn[u])
                    ans2++;
            }
        }
        else if(v!=fa){
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn==1&&rootsum>1)
        ans1++;
}

点双连通分量

点双连通分量定义方式1:任两点间有两条点不重复的路径
点双连通分量定义方式2:不含割点的无向连通图
割点可以属于多个点双连通分量,其余点和每条边属于且只属于一个点双连通分量。
其实就是在求割点的时候退栈储存

边双连通分量

边双连通分量定义方式1:任两点间有两条边不重复的路径
边双连通分量定义方式2:不含割边的无向连通图
割边不属于任何一个边双连通分量,其余的边和每个顶点都属于且只属于一个边双连通分量。
其实就是在求割边的时候退栈储存


例题 POJ 3352 Road Construction

给你一个图,要求你加入最少的边,使得最后得到的图为一个边双连通分量


在开始做之前,我们先学习一个性质

一棵有n个叶子结点的树,至少(只需)要添加ceil(n/2)(向上取整)条边,才(就)能转变为一个边双连通分量。

证明

首先,显然,一条边最多只能连接两个叶子结点,因而至少是ceil(n/2)条边
下证ceil(n/2)条边一定能保证成为一个边双连通分量
首先证明n为偶数时其成立
设两个叶子节点a,b
现在我们想找到离a、b最近的a’、b’是度数>=3的节点
这样的节点不存在当且仅当整个树是一条链(或者可以拉成一条链)且a、b是链的两端,那么我们连接a、b即可
当a’≠b’时
我们连接a、b,再缩点
则减少了两个叶子结点,由于a’、b’度数>=3,所以没有新增叶子节点
当a’=b’时
由于a’度数>=3,则必存在c为叶子结点且a≠c,b≠c
连接a、c则减少两个叶子结点,没有新增叶子结点
综上,n为偶数时,n/2条边可以将其缩为一个点(即成为边双联通分量)
当n为奇数时,我们可以先按上面方法连接n-1个叶子结点,共用了floor(n/2)(向下取整)条边,最后再使那个节点与前面缩成的节点相连即可
综上,证毕


所以上面那道题,首先跑tarjan,缩点重构图,然后统计叶子结点数目再套结论即可

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值