[NOIP模拟][双连通分量]建设图

样例输入:

7 7
1 2
2 3
3 4
2 5
4 5
5 6
5 7

样例输入:

2

题目分析:
题目说要使得任何一条道路损坏后,任意两个城市也可以相互到达,这不就是双连通分量的定义吗,所以这道题就是要我们把原图加边加成一个双连通。再考虑原图,它可能含有一些双连通分量,这些双连通分量内部已经符合要求,只是与外面的点还不符合要求。那么可以先缩点,原图就成为了一棵树(题目保证原有道路可以使得所有城市连到)。于是问题转化为加最少的边将一棵树变成双连通的。通过画图发现,其实加的边数等于叶子节点数+1除以2。结论解释:叶子节点数是指那些连边个数为1的点;除法当然是指计算机int除法。注意,画图连边的方法,应该是将最左边的叶子节点和最右边的叶子节点相连,然后次左和次右连~~~,这样才是最优的。最后如何求叶子节点数,你可以dfs(注意根节点有可能就是一个叶子节点,并且有可能原图已经双连通了),也可以直接就统计缩点后那些点连的双连通分量外的边的个数。
附代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<algorithm>
#include<queue>
using namespace std;

const int N=1e5+100;
const int M=2e5+100;
int tot,first[N],nxt[M*2],to[M*2],times,top,cnt,pd[N],dfn[N],low[N],po;
int ans,sum,stack[N],n,m,maxnum,num[N];
bool flag[N];

int readint()
{
    char ch;int i=0,f=1;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') {ch=getchar();f=-1;}
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<3)+(i<<1)+ch-'0';
    return i*f;
}

void create(int x,int y)
{
    tot++;
    nxt[tot]=first[x];
    first[x]=tot;
    to[tot]=y;
}

void dfs_tarjan(int u,int from)
{
    times++;
    dfn[u]=times;
    low[u]=times;
    stack[top++]=u;
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];
        if(dfn[v]==0)
        {
            dfs_tarjan(v,e);
            low[u]=min(low[u],low[v]);
        }
        else
            if((e^1)!=from) low[u]=min(low[u],dfn[v]);  
    }
    if(dfn[u]==low[u])
    {
        cnt++;
        while(top>=1&&stack[top]!=u)
        {
            top--;
            pd[stack[top]]=cnt;//缩点
        }
    }
}

int main()
{
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);

    int x,y;
    n=readint();m=readint();
    tot=1;
    for(int i=1;i<=m;i++)
    {
        x=readint();y=readint();
        create(x,y);
        create(y,x);
    }
    dfs_tarjan(1,0);//tarjan求双连通
    tot=0;
    for(int i=1;i<=n;i++)
    {
        for(int e=first[i];e;e=nxt[e])
        {
            int v=to[e];
            if(pd[v]!=pd[i]&&flag[v]==false)//记录缩点后的点连的边数
            {
                num[pd[i]]++;
                num[pd[v]]++;
            }
        }
        flag[i]=true;
    }
    for(int i=1;i<=cnt;i++)
        if(num[i]==1) sum++;//统计叶子节点个数
    if(sum%2==1) ans=sum/2+1;
    else ans=sum/2;//其实偶数可以和奇数合成一步,+1对于偶数不影响答案
    printf("%d",ans);

    return 0;
}
发布了99 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qianguch/article/details/78330969