RevolC FaeLoN UVA - 10972

RevolC FaeLoN UVA - 10972

图论·边-双连通分量

http://blog.csdn.net/l123012013048/article/details/47373065

题目大意:

把无向图的每一条边变成一条有向边,形成一个并加入最少的有向边,让新图称为一个强连通分量。问最小加边数。

题解:

求出所有的边-双连通分量,把他们缩点,得到多棵无根树森林。
树上的点是边-双连通分量,边是桥。

对于一个连通块,如果它的节点个数>1:

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

另外,两个连通块的互相连接并不需要额外的边。
只要把给他们加的边各自拆开一条,就能把他们连起来连到一起。

对于一个孤立的点,它需要一条边:

如果有n个点,要求在这n个点间添加有向边,使得这n个点变成强连通,那么需要添加的边的数量为n,均摊每个点一条。

有了上面这个结论,求的时候就比较好办了,处理的时候,只需要统计出所有度为1和度为0的缩点的数量即可 。
假设度为1的数量为A (叶子结点,连通图里面的)
度为2的数量为B(孤立的点)
那么所需要连的边就是(A + 1) / 2 + B

注意求边-双连通分量的时候,不能把当前u取出来,而是只取到v。
在dfs外面要把栈里剩下的元素作为一个新的双联通分量。

Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
#include <vector>
#define MP make_pair
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef pair<int,int> pii;
const int N = 2017;
const int M = 20017;

int n,m;

struct Edge{
    int u,v,next;
}e[M];
int head[N], ec=0;
void clear(){ memset(head,0,sizeof(head)); ec=0; }
void add(int a,int b){
    ec++; e[ec].u=a; e[ec].v=b; e[ec].next=head[a]; head[a]=ec;
}

int belong[N],bridge[M][2],low[N],dfn[N],du[N],tim,bcc_cnt,bridge_cnt;
stack<int> s; 

void dfs(int u,int pa){
    low[u]=dfn[u]=++tim;
    s.push(u);
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].v;
        if(!dfn[v]){
            dfs(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u]){
                bridge_cnt++;
                bridge[bridge_cnt][0]=u; bridge[bridge_cnt][1]=v;
                bcc_cnt++;
                while(true){
                    int x=s.top(); s.pop();
                    belong[x]=bcc_cnt;
                    if(x==v) break;
                }
            }
        }
        else if(dfn[v]<dfn[u] && v!=pa){
            low[u]=min(low[u],dfn[v]);
        } 
    }
}

void find_bcc(){
    bcc_cnt=0; tim=0; bridge_cnt=0;
    memset(dfn,0,sizeof(dfn));
    memset(belong,0,sizeof(belong));
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            dfs(i,-1);
            bcc_cnt++;
            while(!s.empty()){
                int x=s.top(); s.pop();
                belong[x]=bcc_cnt;
            }
        }
    }
}

int main(){
    freopen("a.in","r",stdin);
    while(~scanf("%d%d",&n,&m) && (n||m)){
        clear();
        int a,b;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&a,&b);
            add(a,b); add(b,a);
        }
        find_bcc();
//      D(bcc_cnt); E;
//      for(int i=1;i<=n;i++){
//          D(belong[i]);
//      } E;
        if(bcc_cnt==1){
            printf("0\n");
            continue;
        }
        memset(du,0,sizeof(du));
        for(int i=1;i<=bridge_cnt;i++){
            du[belong[bridge[i][0]]]++; 
            du[belong[bridge[i][1]]]++;
        }
//      for(int i=1;i<=bcc_cnt;i++) D(du[i]); E;
        int A=0,B=0;
        for(int i=1;i<=bcc_cnt;i++){
            if(du[i]==1) A++;
            if(du[i]==0) B++;
        }
//      D(ans); E;
        printf("%d\n",(A+1)/2+B);
    }
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值