POJ 3177 Redundant Paths 边双连通分量+缩点

题目链接:

poj3177




题意:

给出一张连通图,为了让任意两点都有两条通路(不能重边,可以重点),至少需要加多少条边




题解思路:

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

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

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

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

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


代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 5050
using namespace std;
struct node
{
    int to,tag,next;
} edge[maxn*4];
int head[maxn];
int s;

int dfn[maxn],low[maxn],num;

int stack[maxn],top;

int belong[maxn],block;   //连通块

void init()
{
    memset(head,-1,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    block=num=top=s=0;
}

void addedge(int a,int b)
{
    edge[s]= {b,0,head[a]};
    head[a]=s++;
}

void Tarjan(int u,int pre)
{
    dfn[u]=low[u]=++num;
    stack[top++]=u;
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].to;
        if(!dfn[v])
        {
            Tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<low[v])
            {
                edge[i].tag=1;                //
                edge[i^1].tag=1;              //标记割边
            }
        }
        else if(pre!=v)
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])                        //找到一个边双连通分量的根节点
    {
        int d;
        block++;
        while(1)
        {
            d=stack[--top];
            belong[d]=block;                 //缩点 类似并查集的father[]
            if(d==u)
                break;
        }
    }
}
int main()
{
    int degree[maxn];
    int ans;
    int n,m,a,b;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        while(m--)
        {
            scanf("%d%d",&a,&b);
            addedge(a,b);
            addedge(b,a);
        }

        Tarjan(1,-1);
        memset(degree,0,sizeof(degree));
        ans=0;


        for(int i=1; i<=n; i++)                      //缩点后 不需要构图 只需要判断顶点度数即可
        {
            for(int j=head[i]; j!=-1; j=edge[j].next)
                if(edge[j].tag==1)                   //缩点后只有割边才能成为这些"点"的边
                    degree[belong[i]]++;
        }
        for(int i=1; i<=block; i++)
            if(degree[i]==1)
                ans++;

        cout<<(ans+1)/2<<endl;
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值