poj3352Road Construction 边双连通+伪缩点

/*
对于边双连通分支,求法更为简单。只需在求出所有的桥以后,把桥边删除,\
原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何
一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。
一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,
然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,
再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,
就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远
的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,
因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,
恰好是(leaf+1)/2次,把所有点收缩到了一起。
*/
/*
(1) n 为顶点数, 标号从 1 开始
(2) c 为原图的邻接表, g 为 E_BCC 图的邻接表
(3) num[u] 表示原图中的点 u 属于新图中的第 num[u] 个 E_BCC
(4) edge[] 存储所有的桥
(5) 注意 pool[M] 要开得足够大以容得下新旧两个图中所有的边
(6) E_BCC 图中去掉了自环 ( 显然不存在多重边 )
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 11115;
const int M = 2000005;

struct List {
    int v, id;
    List *next;
} pool[M], *c[N], *g[N], *pp;
//c 为原图的邻接表, g 为 E_BCC 图的邻接表
//注意 pool[M] 要开得足够大以容得下新旧两个图中所有的边
inline void add_edge(int u, int v, int id, List *c[])
{
    pp->v = v;
    pp->id = id;
    pp->next = c[u];
    c[u] = pp ++;
}

struct Edge {
    int u, v;
} edge[M];
//edge[] 存储所有的桥,u,v为桥的两个顶点
int n, m, label, tot, top;
int low[N], dfn[N], num[N], stack[N];
bool eflag[M];
//label时间戳,tot连通块数
//dfn用来保存时间戳(次序)编号,low保存顶点i或i的子树最早的次序编号
//num[u] 表示原图中的点 u 属于新图中的第 num[u] 个 E_BCC
void E_BCC_VISIT(int u)
{
    low[u] = dfn[u] = label ++;
    stack[++ top] = u;
    for(List *p = c[u]; p; p = p->next) {
        int v = p->v;
        if(eflag[p->id])    continue;
        eflag[p->id] = true;
        //if(dfn[v]) { low[u] <?= dfn[v]; continue; }
        if(dfn[v]){
            if(low[u] > dfn[v]) low[u] = dfn[v];
            continue;
        }
        E_BCC_VISIT(v);
        //low[u] <?= low[v];
        if(low[u] > low[v]) low[u]=low[v];
        if(low[v] > dfn[u]) {
            edge[m].u = u;//第m条桥的两个顶点u,v
            edge[m ++].v = v;
            ++ tot;
            do {
                num[stack[top]] = tot;
            } while( stack[top --] != v );
        }
    }
}
void E_BCC()
{
    int i;
    tot = 0;
    m = 0;/
    for(i = 1; i <= n; ++ i)    dfn[i] = 0, num[i] = -1;
    for(i = 0; i < m; ++ i)     eflag[i] = false;
    for(i = 1; i <= n; ++ i)
        if(dfn[i] == 0) {
            label = 1;
            top = -1;
            E_BCC_VISIT(i);
            ++ tot;
            while( top >= 0 ) {
                num[stack[top]] = tot;
                -- top;
            }
        }
    for(i = 1; i <= tot; ++ i)  g[i] = NULL;
    //for(i = 1; i <= n; ++ i) {//缩点,这题用不着
      //  int u = num[i];//u为一个双连通分量
        //for(List *p = c[i]; p; p = p->next) {
          //  int v = num[p->v];//v是另一个双连通分量
            //if(u != v)  add_edge(u, v, 0, g);//在两个分量间建一条边
        //}
    //}
}

int main()
{
    int i, j, k;
    while( scanf("%d %d", &n, &m) == 2 ) {
        for(i = 1; i <= n; ++ i)    c[i] = NULL;
        pp = pool;
        for(k = 0; k < m; ++ k) {
            scanf("%d %d", &i, &j);
            add_edge(i, j, k, c);
            add_edge(j, i, k, c);
        }
        E_BCC();
        if(m == 0){cout<<0<<endl; continue;}
        int du[N]={0};
        for(int i=0;i<m;i++){//桥即为联通块的之间的边,这里处理伪缩点
            //cout<<num[edge[i].u]<<' '<<num[edge[i].v]<<endl;
            du[num[edge[i].u]]++;//要用num[]映射到连通块编号上计算联通块的度
            du[num[edge[i].v]]++;
        }
        int leaf=0;//树叶
        //cout<<tot<<endl<<m<<endl;
        for(int i=1;i<=tot;i++) if(du[i]==1)leaf++;
        cout<<(leaf+1)/2<<endl;
        //for(int i=0;i<=m;i++)printf("num[%d]:%d\n",i,num[i]);
        //printf("tot:%d m:%d\n",tot,m);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值