Redundant Paths POJ - 3177 (求割边 + 缩点)

题目链接

题意:
给出一个有桥的图,问最少还要增加多少条边才能使得这个图没有割边。

思路:先求出来割边,直接上Tarjan就行,然后把所有的双连通分量都缩成一个点,把图变成一颗树,找到叶子节点的个数ans,然后在叶子节点上两两之间加一条边(如果ans是奇数,从最后剩下的节点引出一条边随便连在其他的叶子节点上就行了)所以答案就是(ans + 1) / 2.

至于具体怎么两两之间加边,引用一下 这位博主的一个思路,如下:

统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int maxn = 4 * 1e4 + 100;

int n, m, tot, num;
int head[maxn], dfn[maxn], low[maxn], pre[maxn], degree[maxn];
bool bridge[maxn];     //标记这条边是否为割边

struct node 
{
    int u, v, next;
}edge[maxn];

inline void Init() {    //初始化
    tot = 2;
    num = 0;
    memset(head, -1, sizeof(head));
    memset(dfn, 0, sizeof(dfn));
    memset(bridge, false, sizeof(bridge));
    memset(degree, 0, sizeof(degree));
}

inline void add(int u, int v) {
    edge[tot].u = u;
    edge[tot].v = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}

void Tarjan(int u, int edge_i)     //求割边
{
    dfn[u] = low[u] = ++num;
    for(int i = head[u]; i != -1; i = edge[i].next) 
    {
        int v = edge[i].v;
        if(!dfn[v])
        {
            Tarjan(v, i);
            low[u] = min(low[u], low[v]);
            if(low[v] > dfn[u])
            {
                bridge[i] = bridge[i^1] = true;
            }
        }
        else if(i != (edge_i^1))
        {
            low[u] = min(low[u], dfn[v]);
        }
    }
}

int finds(int x)    //用并查集来缩点
{
    return pre[x] == x ? x : pre[x] = finds(pre[x]);
}

int main() 
{
    //freopen("in.txt", "r", stdin);
    cin >> n >> m;
    Init();
    int u, v;
    for(int i = 1; i <= m; ++ i)
    {
        scanf("%d%d", &u, &v);
        add(u, v);
        add(v, u);
    }
    for(int i = 1; i <= n; ++ i)       //为了防止这个图不是连通图,在本题中应该所有样例都是连通图
    {
        if(!dfn[i])
        {
            Tarjan(i, 0);
        }
    }
    for(int i = 1; i <= n; ++ i) {    //并查集初始化
        pre[i] = i;
    }
    for(int i = 2; i < tot; i += 2) {    //如果这条边不是割边就合并两个端点
        if(!bridge[i]) {
            pre[finds(edge[i].u)] = finds(edge[i].v);
        }
    }
    for(int i = 2; i < tot; i += 2) {    //统计每个缩点之后的节点的度
        if(bridge[i]) {
            degree[finds(edge[i].u)]++;
            degree[finds(edge[i].v)]++;
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; ++ i)
    {
        if(degree[i] == 1 && finds(i) == i)   //只有度为1的才是叶子节点
        {
            ans++;
        }
    }
    ans = (ans + 1) / 2;
    cout << ans << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/aqa2037299560/article/details/86306177
今日推荐