[双连通分量]poj1679 Road Construction

题意

给出一个连通的无向图,求至少增加多少条边让这个图变成双连通图

思路

其实题目意思很清晰很明白,但是怎么操作才是重点。
那么首先先想想怎么把整个图简化一下, 先求一次双连通分量。
因为边双连通分量本来就是个边双连通图,那么这里面无论怎样增边,都是无意义的,那么就可以把它缩成一个点。
将所有的边双连通分量都缩成一个点之后,原图变成了一棵树。
那么,如何让一棵树变成一个双连通图,是一个问题。
在网上很容易找到要增加的边就等于叶子的数量加一的和除以二,这里设叶子的数量为 nleaves ,那么答案就是 (nleaves+1)÷2
怎么证明是个问题啊……
其实很容易想到,在一棵树上面,选取到根节点最远的两个叶子节点,然后它们之间连一条边,就可以发现这里产生了一个环,这个环是双连通的。容易发现,如果换成其他的加边的方法, 都没有这种方法产生的环所含的节点多(或一样多),所以这样做一定是最优的。
然后把这个环缩成一个点,然后把这个点作为原图的根节点,然后反复操作,最终得到的答案就是加了 (nleaves+1)÷2 条边
但是为什么叶子的个数总是为1<-_<-,有待思考<-_<-,或者哪个大神来解答都行<-_<-

代码

关于代码,其实是有些和之前求点双连通分量是不太一样的。
因为可以用更加简单的方式去求。
首先先找到原图中所有的桥,然后桥把原图分成了许多双连通分量,即去掉桥之后,剩下的连通分量刚好对应原图的双连通分量
找到桥之后做dfs,标记点,注意别经过桥就可以了
然后找叶子这一块,其实缩点之后是可以不用考虑的,直接把所属的双连通分量的标号拿来就用
然后统计每一个子节点的度数,因为是无向图嘛,所以总度数为2的点就只连了一条边,也就是叶子节点了。

#include <algorithm>
#include <cstring>
#include <cstdio>
#include <queue>

using namespace std;

const int MAXN = 5010,
          MAXM = 20010;

struct Edge {
    int u, v, ne;
} e[MAXM];

int head[MAXN];
int n, m;
int m_cnt;

int bcc_cnt, pre[MAXN], low[MAXN], dfs_clock, bccno[MAXN];
bool is_bridge[MAXM];
int d[MAXN];

int dfs(int u, int fa) {
    int lowu = pre[u] = ++dfs_clock;
    for(int i = head[u]; ~i; i = e[i].ne) {
        int v = e[i].v;
        if(!pre[v]) {
            int lowv = dfs(v, u);
            lowu = min(lowu, lowv);
            if(lowv > pre[u]) {
                is_bridge[i] = 1;
                is_bridge[i ^ 1] = 1;
            }
        } else {
            if(pre[v] < pre[u] && v != fa) {
                lowu = min(lowu, pre[v]);
            }
        }
    }
    low[u] = lowu;
    return lowu;
}

void dfs2(int u) {
    if(bccno[u]) {
        return ;
    }
    bccno[u] = bcc_cnt;
    for(int i = head[u]; ~i; i = e[i].ne) {
        if(!is_bridge[i]) {
            dfs2(e[i].v);
        }
    }
}

void find_bcc(int n) {
    memset(pre, 0, sizeof pre);
    memset(is_bridge, 0, sizeof is_bridge);
    dfs_clock = 0;
    for(int i = 1; i <= n; ++i) {
        if(!pre[i]) {
            dfs(i, -1);
        }
    }

    memset(bccno, 0, sizeof bccno);
    bcc_cnt = 1;

    for(int i = 1; i <= n; ++i) {
        if(!bccno[i]) {
            dfs2(i);
            ++bcc_cnt;
        }
    }
}

void AddEdge(int u, int v) {
    e[m_cnt].u = u;
    e[m_cnt].v = v;
    e[m_cnt].ne = head[u];
    head[u] = m_cnt++;
}

void init() {
    memset(head, -1, sizeof head);
    m_cnt = 0;
}

int main(void) {
    init();
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        AddEdge(u, v);
        AddEdge(v, u);
    }

    find_bcc(n);

    for(int i = 1; i <= n; ++i) {
        for(int j = head[i]; ~j; j = e[j].ne) {
            if(bccno[i] != bccno[e[j].v]) {
                d[bccno[i]]++;
                d[bccno[e[j].v]]++;
            }
        }
    }

    int cnt = 0;
    for(int i = 1; i < bcc_cnt; ++i) {
        if(d[i] == 2) {
            cnt++;
        }
    }
    printf("%d\n", (cnt + 1) >> 1);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值