HDU4612 Warm up (加一条边使得剩下的桥最少)

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=4612

题目

 N planets are connected by M bidirectional channels that allow instant transportation. It’s always possible to travel between any two planets through these channels.
  If we can isolate some planets from others by breaking only one channel , the channel is called a bridge of the transportation system.
People don’t like to be isolated. So they ask what’s the minimal number of bridges they can have if they decide to build a new channel.
  Note that there could be more than one channel between two planets.

Input
  The input contains multiple cases.
  Each case starts with two positive integers N and M , indicating the number of planets and the number of channels.
  (2<=N<=200000, 1<=M<=1000000)
  Next M lines each contains two positive integers A and B, indicating a channel between planet A and B in the system. Planets are numbered by 1..N.
  A line with two integers ‘0’ terminates the input.

Output
  For each case, output the minimal number of bridges after building a new channel in a line.

Sample Input
4 4
1 2
1 3
1 4
2 3
0 0

Sample Output
0

题意

给定一个无向图(不保证连通,需要对每一个连通图tarjan一次),加一条边使得剩下的桥最少。

分析

求出所有边双连通分量,将分量缩点,对缩点建新图(一个树图)。加一条边(u,v)使减少的桥最多,而减少的桥数等于u–>LCA(u,v)–>v–>u这个环上的桥数。即只需选两点使得u,v在树上的距离最远(树的直径的定义:树上最长的简单路径)。两遍dfs求出树的直径即可。

注意重边问题,WA了多次。如果边(u,v)有重边,边一定不是桥,重边相当于两点之间的另一条路(比如从u到u坐高铁去和坐火车去),不能跳过不访问。标记跳过不访问应该是一条无向边的反向边。建深搜树是访问了父子边(u,v),对其回边(v,u)需跳过访问。

AC代码

//1170ms 43.9MB
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <stack>
using namespace std;

const int maxn=2e5+100;
const int maxe=1e6+100;
struct edge//邻接表表示图
{
    int to,next;
    int flag;//访问标记
}e[maxe<<1];
int head[maxn],cnt;
stack<int> sta;
int dfn[maxn],low[maxn],belong[maxn],index,n,m,sum,scc;
int fa[maxn];//父结点
int bridge[maxn][2];
void init()//初始化
{
    memset(head,-1,sizeof(head));
    cnt=-1;//边编号
    scc=0;//边双连通分量编号
    index=0;//深度
    sum=0;//桥总数
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
    memset(fa,0,sizeof(fa));
}
void add_edge(int u,int v)//加边
{
    e[++cnt].to=v;
    e[cnt].flag=0;//标记该无向边是否被访问过,将正向边和反向边一起标记
    e[cnt].next=head[u];
    head[u]=cnt;
}
void tarjan(int u,int f)//dfs确定边双连通分量和桥
{
    dfn[u]=low[u]=++index;
    sta.push(u);
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(e[i].flag) continue;///只能跳过反向边,不能跳过重边
        ///注意重边,边(u,v)有重边,其不可能是桥,重边相当于两点之间的另一条路,不能合并
        e[i].flag=e[i^1].flag=1;
        if(dfn[v])
            low[u]=min(low[u],dfn[v]);
        else
        {
            tarjan(v,u);
            fa[v]=u;
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u])//桥的判断条件
            {
                sum++;
                //存桥,然后根据桥去建新图
                bridge[sum][0]=u;
                bridge[sum][1]=v;
            }
        }
    }
    if(dfn[u]==low[u])//双连通分量
    {
        scc++;
        while(!sta.empty())
        {
            int p=sta.top();sta.pop();
            belong[p]=scc;
            if(p==u) break;
        }
    }
}
int node,d[maxn],ans;
void dfs(int u,int dist)//dfs确定每个结点距初始结点的距离
{
    d[u]=dist;
    if(d[u]>ans) ans=d[u],node=u;
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(d[v]!=-1) continue;
        dfs(v,dist+1);
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0 && m==0) break;
        init();
        while(m--)//建图
        {
            int u,v;
            scanf("%d%d",&u,&v);
            add_edge(u,v);
            add_edge(v,u);
        }
        for(int i=1;i<=n;i++)//初始图不一定连通
            if(!dfn[i]) tarjan(i,0);//tarjan算法求所有双连通分量和桥

        //根据桥建新图
        memset(head,-1,sizeof(head));
        cnt=-1;
        for(int i=1;i<=sum;i++)
        {
            int u=belong[bridge[i][0]];
            int v=belong[bridge[i][1]];
            add_edge(u,v);
            add_edge(v,u);
        }
        //两遍dfs求树直径
        ans=0;
        memset(d,-1,sizeof(d));
        dfs(1,0);//从根到最远点node
        memset(d,-1,sizeof(d));
        ans=0;
        dfs(node,0);//从最远点到另一个最远点
        printf("%d\n",sum-ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37685156/article/details/80495679
今日推荐