分离的路径题解

luogu:P2860 [USACO06JAN]Redundant Paths G

分析:

  题目要求添加道路,是任意两点有两条分离的路径。如下图,给出了一个样例的解释。

 

image

 

  首先,我们分析一些性质:

    性质1:对于无向图,在一个环中,显然,任意两个点都有两条分离的路径,所以我们不考虑环之间的边。

    性质2:对于无向图,进行边双联通分量缩点后,所建的新图是一颗树。

  So,根据性质1,我们可以把问题转换为求添加最少边使图可以缩为一个点(或者说是形成环)。

  So,根据性质1和性质2,我们可以先对图进行边双缩点,然后对树进行操作。

  首先考虑样例,缩点并重新标点后如下图:

    

 

  对于此图,我们在1,4和4,3之间加两条边,整个图便再缩为一个点。

  可以观察到,添加边的几个节点1,4,3都是以2为根的树的叶节点,那么我们可以大胆猜测添加的边数为以出度大于1的节点为根的树的叶节点个数/2向上取整(出度为1的节点个数/2向上取整)。

  这个贪心的简单解释就是每两个叶节点相连后与两点到根节点的路径形成一个环然后缩为一个点;如果叶节点数为奇数,则两两相连后会剩下一个叶节点,再把此叶节点与根节点相连,边数再+1。

  然后,其他样例,如下图为已缩图:

  

  共有5个出度为1的叶节点,根据上述做法,需要添加3条边。

  当我们以2为根节点时注意,如果我们5,6之间添加一条边,那么只有4,5,6号节点形成了环,不满足两点与根节点形成环的要求,应当添加6,8和5,7和2,1三条边。

  多试几组数据,可以发现这个思路大概是正确的(其实是我不会证明),然后就可以写代码了。

复制代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=5e3+5;
int n,m;
int head[MAXN],to[MAXN<<2],nxt[MAXN<<2],tot=1;
int hc[MAXN],tc[MAXN<<2],nc[MAXN],totc=0;
int dfn[MAXN],low[MAXN],number=0;
bool bridge[MAXN<<2],vis[MAXN],vs[MAXN];
int cnt=0,c[MAXN],ans=0,sum=0,root;
void add(int x,int y)//建图
{
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}
void add_c(int x,int y)//建新图
{
    tc[++totc]=y;
    nc[totc]=hc[x];
    hc[x]=totc;
}
void tarjan(int x,int in_edge)//tarjan求桥
{
    dfn[x]=low[x]=++number;
    for(int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(!dfn[y])
        {
            tarjan(y,i);
            low[x]=min(low[x],low[y]);
            if(dfn[x]<low[y])
            {
                bridge[i]=bridge[i^1]=1;
            }
        }
        else if(i!=(in_edge^1))
            low[x]=min(low[x],dfn[y]);
    }
}
void dfs(int x)//dfs缩点
{
    vis[x]=1;
    c[x]=cnt;
    for(int i=head[x];i;i=nxt[i])
    {
        if((!vis[to[i]])&&(!bridge[i]))
        {
            dfs(to[i]);
        }
    }
}
void count(int x)//dfs数叶节点个数
{
    vs[x]=1;
    int l=0;
    for(int i=hc[x];i;i=nc[i])
    {
        int y=tc[i];
        if(vs[y]) continue;//此处可过滤重边
        count(y);
        l++;
    }
    if(l==0) sum++;//没有儿子,是叶节点
    if(x==root&&l==1) sum++;//根节点只有1个儿子,是叶节点
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if(x==y) continue;
        add(x,y);
        add(y,x);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i]) tarjan(i,0);
    for(int i=1;i<=n;i++)
        if(!vis[i]) cnt++,dfs(i);
    if(cnt==1)//如果缩点后只有一个点,就不用新建边了,直接输出0
    {
        printf("0\n");
        return 0;
    }
    for(int x=1;x<=n;x++)//建新图
    {
        for(int i=head[x];i;i=nxt[i])
        {
            int y=to[i];
            if(c[x]==c[y]) continue;
            add_c(c[x],c[y]);
            add_c(c[y],c[x]);
        }
    }
    for(int i=1;i<=cnt;i++)
        if(!vs[i]) root=i,count(i);
    printf("%d\n",(sum+1)/2);//叶节点个数/2向上取整
    return 0;
}
复制代码

  完。

 

posted @   zxr123_is_dd  阅读(116)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示