这个题最开始我想的是,直接缩点求双连通分量,连接这些双联通分量不就行了吗?

但是其实是不对的,双连通内部双联通,我们如果任意的连接一条边在这些双联通分量之间,他们之间有没有桥其实并不知道。

我应该是求缩点以后的叶子节点的个数,因为叶子节对于其本身来说,只有一条桥于其相连,我们可以连接两个叶子节点。那么这个两个区域就合成了一个双联通分量区域。同时为了减少边的使用,我们可以连接两个都是叶子节点的点。最后+1,剩下一个也应该连接。这样就能非常容易的算出把图变成双连通,所需要的边的数目。

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int N = 1e4+5,M = 1e5+5;
int head[N],Next[M],ver[M],low[M],dfn[M],stack[M];
int block;
int belong[M],deg[M];
bool brige[M],ins[M];
int c[M],dcc,top;
int tot,n,m,num,cnt;
void add(int x,int y)
{
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
void tarjan(int u,int pre)
{
    int v;
    dfn[u]=low[u]=++num;
    stack[++top]=u;
    ins[u]=1;
    for (int i=head[u]; i; i=Next[i])
    {
         v=ver[i];
        if (v==pre)continue;
        if (!dfn[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if (low[v]>dfn[u])
            {
                brige[i]=1;//标记为桥
                brige[i^1]=1;
                cnt++;
            }
        }
        else low[u]=min(low[u],dfn[v]);
    }
    if (low[u]==dfn[u]){//如果当前节点是一个根节点 也就是说我们DFS完了这个连通分量
        cnt++;
        do
        {
            v=stack[top--];//把这个连通分量里面的所有点都拿出来
            ins[v]=0;
            c[v]=cnt;//标记上所在的连通分量
        }while(u!=v);
    }
}
void init(){
   memset(Next,0,sizeof(Next));
   memset(low,0,sizeof(low));
   memset(brige,0,sizeof(brige));
   memset(head,0,sizeof(head));
   memset(ver,0,sizeof(ver));
   memset(dfn,0,sizeof(dfn));
   memset(c,0,sizeof(c));
   tot=1;
   cnt=0;
   num=0;
   dcc=0;
   top=0;
}
int main()
{
    int u,v;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for (int i=1; i<=m; i++)
        {
            scanf("%d%d",&u,&v);
            add(u,v);
            add(v,u);
        }
        for (int i=1;i<=n;i++){
            if (!dfn[i]){
                tarjan(i,i);
            }
        }
        for (int i=1;i<=n;i++){
            for (int j=head[i];j;j=Next[j])
            {
                  if (brige[j])   //缩点后把桥两边的缩点的度++
                    deg[c[i]]++;
            }
        }
        int ans=0;
        for (int i=1;i<=cnt;i++){
            if (deg[i]==1){//我们找出缩点后度为1的点,也就是叶子节点,
                ans++;
            }
        }
        printf("%d\n",(ans+1)/2);//那么把图连接成双联通分量的所需边=(缩点后叶子节点的个数+1)/2
        //因为很简单 我把叶子节点两两连接起来,那么这样消耗的边也就是最少的。这样我们就能让图
        //变成双联通的
     }
    return 0;
}