POJ3177.Redundant Paths——增加多少条边使原图变为边双连通图

http://poj.org/problem?id=3177

题目描述:
有n个牧场,Bessie 要从一个牧场到另一个牧场,要求至少要有2条独立的路可以走。现已有m条路,求至少要新建多少条路,使得任何两个牧场之间至少有两条独立的路。两条独立的路是指:没有公共边的路,但可以经过同一个中间顶点。

分析:在同一个边双连通分量中,任意两点都有至少两条独立路可达,所以同一个边双连通分量里的所有点可以看做同一个点。

缩点后,新图是一棵树,树的边就是原无向图的桥。

现在问题转化为:在树中至少添加多少条边能使图变为双连通图。

结论:添加边数=(树中度为1的节点数+1)/2

具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。

其实求边双连通分量和求强连通分量差不多,每次访问点的时候将其入栈,当low[u]==dfn[u]时就说明找到了一个连通的块,则栈内的所有点都属于同一个边双连通分量,因为无向图要见反向边,所以在求边双连通分量的时候,遇到反向边跳过就行了。

另外如果low[v]<=dfn[u]说明u,v在同一个边双连通分量中,可以用并查集合并

//220K  0MS C++
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#define clr(a) memset(a,0,sizeof(a))
#define MIN(a,b) ((a)>(b)?(b):(a))
#define N 1005
#define M 20005
using namespace std;
typedef struct NodeStr { //边结点
    int j; //j 为另一个顶点的序号
    struct NodeStr *next; //下一个边结点
} Node;
int n, m; //顶点数、边数
Node mem[M];
int memp; //mem 为存储边结点的数组,memp 为mem 数组中的序号
Node *e[N]; //邻接表
int w; //原图中边双连通分量的个数
int belong[N];
int low[N], dfn[N]; //low[i]为顶点i 可达祖先的最小编号,dfn[i]为深度优先数
int visited[N]; //visited[i]为0-未访问,为1-已访问,为2-已访问且已检查邻接顶点
int bridge[M][2], nbridge;
void addEdge( Node *e[], int i, int j ) //在邻接表中插入边(i,j)
{
    Node *p = &mem[memp++];
    p->j = j;
    p->next = e[i];
    e[i] = p;
}
int FindSet( int f[], int i ) //并查集的查找函数
{
    int j = i, t;
    while( f[j]!=j ) j = f[j];
    while( f[i]!=i ) {
        t = f[i];
        f[i] = j;
        i = t;
    }
    return j;
}
void UniteSet( int f[], int i, int j ) //并查集的合并函数
{
    int p = FindSet(f,i), q = FindSet(f,j);
    if( p!=q ) f[p] = q;
}
void DFS_2conn( int i, int father, int dth, int f[] )
{
    int j, tofather = 0;
    Node *p;
    visited[i] = 1;
    low[i] = dfn[i] = dth;
    for( p=e[i]; p!=NULL; p=p->next ) {
        j = p->j;
        if( visited[j]==1 && (j!=father||tofather) ){
            low[i] = MIN(low[i],dfn[j]);
        }

        if( visited[j]==0 ) {
            DFS_2conn( j, i, dth+1, f );
            low[i] = MIN( low[i], low[j] );
            if( low[j]<=dfn[i] ) UniteSet( f, i, j ); //i,j 在同一个双连通分量
            if( low[j]>dfn[i] ) //边(i,j)是桥
                bridge[nbridge][0] = i, bridge[nbridge++][1] = j;
        }
        if( j==father ) {
                tofather = 1;
        }
    }
    visited[i] = 2;
}
//求无向图极大边双连通分量的个数
int DoubleConnection( )
{
    int i, k, f[N],ncon = 0;
    for( i=0; i<n; i++ ) f[i] = i, belong[i] = -1; //f[]并查集数组
    clr( visited );
    nbridge = 0;
    DFS_2conn( 0, -1, 1, f );
    cout<<"bridge="<<nbridge<<endl;
    for( i=0; i<n; i++ ) {
        k = FindSet( f, i );
        if( belong[k]==-1 ) belong[k] = ncon++;
        belong[i] = belong[k];
    }
    return ncon;
}
int main( )
{
#ifndef ONLINE_JUDGE
freopen("in.cpp","r",stdin);
#endif // ONLINE_JUDGE
    int i, j, k;
    while( scanf( "%d%d", &n, &m ) != EOF ) {
        memp = 0;
        clr(e);
        for( k=0; k<m; k++ ) { //读入边,并插入邻接表中
            scanf( "%d%d", &i, &j );
            i--;
            j--;
            addEdge( e, i, j );
            addEdge( e, j, i );
        }
        w = DoubleConnection( ); //求边双连通分量个数
        int d[N] = { 0 }; //收缩后各顶点的度数
        for( k=0; k<nbridge; k++ ) {
            i = bridge[k][0];
            j = bridge[k][1];
            d[belong[i]]++;
            d[belong[j]]++;
        }
        int count = 0; //收缩后叶子结点的个数
        for( i=0; i<w; i++ )
            if( d[i]==1 ) count++;
        printf( "%d\n", (count+1)/2 );
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值