图(九):双连通分量

双连通分量

双连通分量是针对无向图来说的,双连通分量分为两种:点双连通, 边双连通。

割点:对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点。

桥:对于一个无向图,如果删掉一条边后图中的连通分量数增加了,则称这条边为桥或者割边。

点双连通:在一张连通的无向图中,对于两个点u和v,如果无论删去哪个点(只能删去一个,且不能删 u 和v 自己)都不能使它们不连通,我们就说u和v点双连通

边双连通:在一张连通的无向图中,对于两个点u和v,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说u和v边双连通

一、模板:

P3388 【模板】割点(割顶)

参考代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<set>
using namespace std;
const int N = 1e6 + 10;
int root;
int n, m;
int dfn[N], low[N], timetamp;
vector<int> g[N];
set<int> gd;
void tarjan(int u, int fa){
    dfn[u] = low[u] = ++ timetamp;
    int cnt = 0;
    for(int i : g[u]){
        if(!dfn[i]){
            cnt ++;
            tarjan(i, u);
            low[u] = min(low[u], low[i]);
            if(u != fa && low[i] >= dfn[u] && !gd.count(u)){
                gd.insert(u);
            }
        }
        else{
            low[u] = min(low[u], dfn[i]);
        }
        
    } 
    if(u == fa && cnt >= 2 && !gd.count(u)){
        gd.insert(u);
    }
    return ;
}
int main(){
    scanf("%d%d",&n, &m);
    for(int i = 0; i < m; i++){
        int a, b;
        scanf("%d%d",&a, &b);
        g[a].push_back(b);
        g[b].push_back(a);
    }
    for(root = 1; root <= n ; root ++){
        if(!dfn[root]){
            tarjan(root, root);
        }
    }
    printf("%ld\n", gd.size());
    for(auto i : gd){
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

二、应用

We Need More Bosses

思路: 对原图求边双连通分量,再对缩点之后的图求直径即可。

参考代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 3e5 + 10;
vector<int> g[N], h[N];
int n, m;
int dfn[N], low[N], timetamp;
int start, en, ans;
int dist[N];
int id[N];
int scc_cnt, scc_size[N];
int stk[N], top;
bool st[N];
void tarjan(int u, int fa){
    dfn[u] = low[u] = ++ timetamp;
    stk[++ top] = u;
    st[u] = true;
    for(int i : g[u]){
        //printf("****%d %d\n", i, u);
        if(!dfn[i]){
            tarjan(i, u);
            low[u] = min(low[u], low[i]);
        }
        else if(i != fa){
            low[u] = min(low[u], dfn[i]);
        }
    }
    if(low[u] == dfn[u]){
        int y;
        ++ scc_cnt;
        do{
            y = stk[top --];
            st[y] = false;
            id[y] = scc_cnt;
            scc_size[scc_cnt] ++;
        }while(y != u);
    }
}

void dfs(int u, int f){
    dist[u] = dist[f] + 1;
    for(int i : h[u]){
        if(i != f){
            dfs(i, u);
        }
    }
}
signed main(){
    scanf("%lld%lld",&n, &m);
    for(int i = 0; i < m ; i ++){
        int a, b;
        scanf("%lld%lld",&a, &b);
        g[a].push_back(b);
        g[b].push_back(a);
    }
    tarjan(1, -1);
    for(int i = 1; i <= n ; i ++){
        for(int j : g[i]){
            int a = id[i];
            int b = id[j];
            if(a != b){
                h[a].push_back(b);
            }
        }
    }
    /*
    printf("%lld\n", scc_cnt);
    for(int i = 1; i <= n ; i ++){
        printf("%lld ",id[i]);
    }
    printf("\n");
    */
    dfs(1, 0);
    for(int i = 1; i <= scc_cnt; i ++){
        if(dist[i] > dist[start]){
            start = i;
        }
    }
    dfs(start, 0);
    for(int i = 1; i <= scc_cnt; i ++){
        ans = max(ans, dist[i] - 1);
    }
    printf("%lld\n", ans);
}

冗余路径

题意: 给定一张无向图,在其基础上添加一些无向边,使得每个点之间至少存在两条相互分离的路径。

思路:

首先对原图求边双连通分量, 做完后整张图转换为只剩下点和桥的树,树的叶子结点即需要添加边的点,当叶子个数为偶数时,直接将叶子两辆相连。当叶子个数为奇数时,两辆相连之后剩余的一个叶子连向树上任意一条边即可。综上所述答案为(cnt(叶子节点个数) + 1) / 2 。

参考代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 5010, M = 4e4 + 10;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timetamp;
int n, m;
int stk[N], top;
int d[N];
int id[N];
int dcc_cnt, dcc_size[N];
bool st[N], is_bridge[M];
void add(int a, int b){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}
void tarjan(int u, int fa){
    dfn[u] = low[u] = ++timetamp;
    stk[++ top] = u;
    st[u] = true;
    for(int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        if(!dfn[j]){
            tarjan(j, i);
            low[u] = min(low[u], low[j]);
            if(dfn[u] < low[j]){
                is_bridge[i] = is_bridge[i ^ 1] = true;
            }
        }
        else if(i != (fa ^ 1)){
            low[u] = min(low[u], dfn[j]);
        }
    }
    if(low[u] == dfn[u]){
        int y;
        ++dcc_cnt;
        do{
            y = stk[top --];
            st[y] = false;
            id[y] = dcc_cnt;
            dcc_size[dcc_cnt] ++;
        }while(y != u);
    }
}
int main(){
    scanf("%d%d",&n, &m);
    memset(h, -1, sizeof(h));
    for(int i = 0; i < m ; i ++){
        int a, b;
        scanf("%d%d",&a, &b);
        add(a, b);
        add(b, a);
    }
    tarjan(1, -1);
    for(int i = 0; i < idx ; i ++){
        if(is_bridge[i]){
            d[id[e[i]]] ++;
        }
    }
    int cnt = 0;
    for(int i = 1; i <= dcc_cnt; i ++){
        if(d[i] == 1){
            cnt ++;
        }
    }
    printf("%d\n",(cnt + 1) / 2);
}

电力

很裸的点双连通,直接套模板即可。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1e4 + 10;
int dfn[N], low[N], timetamp;
int root, ans;
int n, m;
vector<int> g[N];
void tarjan(int u){
    dfn[u] = low[u] = ++ timetamp;
    int cnt = 0;
    for(int i : g[u]){
        if(!dfn[i]){
            tarjan(i);
            low[u] = min(low[u], low[i]);
            if(low[i] >= dfn[u]){
                cnt ++;
            }
        }
        else {
            low[u] = min(low[u], dfn[i]);
        }
    }
    if(u != root && cnt){
        cnt ++;
    }
    ans = max(ans, cnt);
}
int main(){
    while(~scanf("%d%d",&n, &m)){
        if(n == 0 && m == 0){
            break;
        }
        memset(dfn, 0, sizeof(dfn));
        timetamp = ans = 0;
        int cnt = 0;
        for(int i = 0; i < m ; i ++){
            int a, b;
            scanf("%d%d",&a, &b);
            g[a].push_back(b);
            g[b].push_back(a);
        }
        for(root = 0; root < n ; root ++){
            if(!dfn[root]){
                cnt ++;
                tarjan(root);
            }
        }
        for(int i = 0; i < n; i ++){
            g[i].clear();
        }
        printf("%d\n", ans + cnt - 1);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值