hdu 4612 Warm up

15 篇文章 0 订阅
4 篇文章 0 订阅

Problem

acm.hdu.edu.cn/showproblem.php?pid=4612
vjudge.net/contest/129990#problem/E

Reference

Tarjan三大算法之双连通分量(割点,桥)
Tarjan三大算法之双连通分量(双连通分量)
hdu 4612 Warm up(无向图Tarjan+树的直径)
HDU-4612-Warm up(无向图缩点+直径)

Meaning

一幅 n 个点、m 条无向边的联通图,现要加一条边,使得图中桥的数量最少,求这个最少的桥数。

Analysis

把无向图中成环的(边双连同分量?)缩成一个点,就得到一棵树,树上的每一条边都对应原图的一座桥。加一条边把树上任意两个点连起来,就会成环,环上的边就都变成不是桥了,所以要减少最多的桥,就把树上最长路径(即树的直径)上的两个端点连起来。答案就是树的边数(即原图的桥数) - 树的直径长
无项图的缩点跟有向图不同,因为如果 f 有边指向 t,那 t 必然有边指回 f,而在有向图中未必。所以在 Tarjan 深搜的时候,能让子结点直接通过反向边找到父结点第一个参考博客里求桥时就有判是否为父结点)。但由于这题有重边,子结点虽不能通过反向边找到父结点,但允许通过重边找到父结点。比如数据:

4 4
1 2
1 3
1 4
2 1

是应该只有 2 座桥,缩点后只有 3 个点。所以用到了一个 vis 数组,在搜边时,同时标记它的反向边,防止子结点通过反向边找父结点,但同时又不影响重边。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
const int N = 200000, M = 1000000;

int head[N+1], from[M<<1], to[M<<1], nxt[M<<1];
bool vis[M<<1];

void add_edge(int f, int t, int sz)
{
    from[sz] = f;
    to[sz] = t;
    nxt[sz] = head[f];
    head[f] = sz;
    vis[sz] = false;
}

int bridge, tm, num;
int dfn[N+1], low[N+1], belong[N+1];
bool instk[N+1];
stack<int> stk;

void tarjan(int now)
{
    dfn[now] = low[now] = ++tm;
    stk.push(now);
    instk[now] = true;
    for(int i = head[now]; ~i; i = nxt[i])
    {
        // 当搜到已搜过的边的反向边时
        // 就应跳过
        if(vis[i])
            continue;
        // 同时标记反向边(主要是用来标记反向边)
        vis[i] = vis[i^1] = true;
        if(!dfn[to[i]])
        {
            tarjan(to[i]);
            low[now] = min(low[now], low[to[i]]);
            if(low[to[i]] > dfn[now])
                ++bridge;
        }
        else if(instk[to[i]])
            low[now] = min(low[now], dfn[to[i]]);
    }
    if(dfn[now] == low[now])
    {
        ++num;
        int t;
        do
        {
            t = stk.top();
            stk.pop();
            instk[t] = false;
            belong[t] = num;
        } while(t != now);
    }
}

queue<int> que;
int dis[N+1];

void bfs(int s)
{
    memset(dis, 0, sizeof dis);
    dis[s] = 1;
    que.push(s);
    for(int tp; !que.empty(); que.pop())
    {
        tp = que.front();
        for(int i = head[tp]; ~i; i = nxt[i])
            if(!dis[to[i]])
            {
                dis[to[i]] = dis[tp] + 1;
                que.push(to[i]);
            }
    }
}

int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m), n || m)
    {
        memset(head, -1, sizeof head);
        for(int i = 0, f, t, sz = 0; i < m; ++i)
        {
            scanf("%d%d", &f, &t);
            add_edge(f, t, sz++);
            add_edge(t, f, sz++);
        }

        // Tarjan 找桥、缩点
        memset(dfn, 0, sizeof dfn);
        memset(instk, false, sizeof instk);
        tm = num = bridge = 0;
        tarjan(1);

        // 重新建图 -> 缩点后的树
        // 直接建在原来的前向星数组就行
        memset(head, -1, sizeof head);
        for(int i = 0, f, t, sz = 0; i < m << 1; i += 2)
        {
            // 注意要先读出 from[i] 和 to[i]
            // 因为它们有可能被新边的数据覆盖
            f = from[i], t = to[i];
            if(belong[f] != belong[t])
            {
                add_edge(belong[f], belong[t], sz++);
                add_edge(belong[t], belong[f], sz++);
            }
        }

        // 找树的直径
        bfs(1);
        int a = 1, b = 0;
        for(int i = 1; i <= num; ++i)
            if(dis[i] > dis[a])
                a = i;
        bfs(a);
        for(int i = 1; i <= num; ++i)
            if(dis[i] > b)
                b = dis[i];

        printf("%d\n", bridge - b + 1);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值