zoukankan      html  css  js  c++  java
  • POJ3352Road Construction(构造双连通图)sdut2506完美网络

    构造双连通图:一个有桥的连通图,如何把它通过加边变成边双连通图

    一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。

    统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。

    题目: http://poj.org/problem?id=3352

    题意:给你一个连通的无向图,现在问你最少在该图中添加几条边,能使得该图变成边双连通图?

    我没有搞懂为什么至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,暂且记住吧。

    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    #include <stack>
    #define N 10010
    using namespace std;
    struct node
    {
        int x,y,w,next,flag;
    } eg[2*N];
    stack<int>q;
    int tt,head[N],dfn[N],low[N],ti,n,m,be[N],bridge[N][2],tp;
    int cnt,de[N];
    void init()
    {
        tt=0;
        ti=0;
        tp=0;
        cnt=0;
        memset(head,-1,sizeof(head));
        memset(dfn,0,sizeof(dfn));
        memset(be,0,sizeof(be));
        memset(de,0,sizeof(de));
        memset(bridge,0,sizeof(bridge));
        while(!q.empty()) q.pop();
    }
    void add(int xx,int yy)
    {
        eg[tt].x=xx;
        eg[tt].y=yy;
        eg[tt].next=head[xx];
        head[xx]=tt++;
    }
    void tarjan(int u,int fa)
    {
        dfn[u]=low[u]=++ti;
        q.push(u);
        for(int i=head[u]; i!=-1; i=eg[i].next)
        {
            int v=eg[i].y;
            if(v==fa) continue;
            if(!dfn[v])
            {
                tarjan(v,u);
                low[u]=min(low[u],low[v]);
                if(low[v]>dfn[u])//边(u,v)为桥,可以统计一个边连通分支
                {
                    bridge[tp][0]=u;
                    bridge[tp++][1]=v;
                    ++cnt;
                    int w;
                    do
                    {
                        w=q.top();
                        q.pop();
                        be[w]=cnt;
                    }
                    while(w!=v);//注意点u并没有出栈,因为点u属于另一个边连通分量
                }
            }
            else
            {
                low[u]=min(dfn[v],low[u]);
            }
        }
    }
    int main()
    {
        int xx,yy;
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            init();
            for(int i=1; i<=m; i++)
            {
                scanf("%d%d",&xx,&yy);
                add(xx,yy);
                add(yy,xx);
            }
            tarjan(1,-1);
            if(!q.empty())
            {
                ++cnt;
                int w;
                do
                {
                    w=q.top();
                    q.pop();
                    be[w]=cnt;
                }
                while(w!=1);
            }
            for(int i=0; i<tp; i++)
            {
                int u=bridge[i][0];
                int v=bridge[i][1];
                de[be[u]]++;
                de[be[v]]++; //统计缩点后的的度
            }
            /*for(int i=0;i<tp;i++)
            {
                printf("bridge==%d %d
    ",bridge[i][0],bridge[i][1]);
            }
            printf("cnt==%d
    ",cnt);*/
            int leaf=0;
            for(int i=1; i<=cnt; i++)
            {
                if(de[i]==1)
                    leaf++;
            }
            printf("%d
    ",(leaf+1)/2);
        }
        return 0;
    }

    第一次交的,需要用数组f[N]防止跨边,我感觉实际上这样是没用的,但大家代码都这么写我就贴一下吧,无向图是没有跨边的。

    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    #include <stack>
    #define N 10010
    using namespace std;
    struct node
    {
        int x,y,w,next,flag;
    } eg[2*N];
    stack<int>q;
    int tt,head[N],dfn[N],low[N],ti,n,m,be[N],bridge[N][2],tp;
    int cnt,de[N],f[N];
    void init()
    {
        tt=0;
        ti=0;
        tp=0;
        cnt=0;
        memset(head,-1,sizeof(head));
        memset(dfn,0,sizeof(dfn));
        memset(be,0,sizeof(be));
        memset(de,0,sizeof(de));
        memset(bridge,0,sizeof(bridge));
        memset(f,0,sizeof(f));
        while(!q.empty()) q.pop();
    }
    void add(int xx,int yy)
    {
        eg[tt].x=xx;
        eg[tt].y=yy;
        eg[tt].next=head[xx];
        head[xx]=tt++;
    }
    void tarjan(int u,int fa)
    {
        dfn[u]=low[u]=++ti;
        q.push(u);
        f[u]=1;
        for(int i=head[u]; i!=-1; i=eg[i].next)
        {
            int v=eg[i].y;
            if(v==fa) continue;
            if(!dfn[v])
            {
                tarjan(v,u);
                low[u]=min(low[u],low[v]);
                if(low[v]>dfn[u])//边(u,v)为桥,可以统计一个边连通分支
                {
                    bridge[tp][0]=u;
                    bridge[tp++][1]=v;
                    ++cnt;
                    int w;
                    do
                    {
                        w=q.top();
                        q.pop();
                        f[w]=0;
                        be[w]=cnt;
                    }
                    while(w!=v);//注意点u并没有出栈,因为点u属于另一个边连通分量
                }
            }
            else if(f[v])
            {
                low[u]=min(dfn[v],low[u]);
            }
        }
    }
    int main()
    {
        int xx,yy;
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            init();
            for(int i=1; i<=m; i++)
            {
                scanf("%d%d",&xx,&yy);
                add(xx,yy);
                add(yy,xx);
            }
            tarjan(1,-1);
            if(!q.empty())
            {
                ++cnt;
                int w;
                do
                {
                    w=q.top();
                    q.pop();
                    be[w]=cnt;
                }
                while(w!=1);
            }
            for(int i=0;i<tp;i++)
            {
                int u=bridge[i][0];
                int v=bridge[i][1];
                de[be[u]]++;
                de[be[v]]++; //统计缩点后的的度
            }
            /*for(int i=0;i<tp;i++)
            {
                printf("bridge==%d %d
    ",bridge[i][0],bridge[i][1]);
            }
            printf("cnt==%d
    ",cnt);*/
            int leaf=0;
            for(int i=1;i<=cnt;i++)
            {
                if(de[i]==1)
                leaf++;
            }
            printf("%d
    ",(leaf+1)/2);
        }
        return 0;
    }
    View Code

     sdut2506:http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=2506

    题目解析:不是很确定自己是不是做对了,只能说数据很水吧,他丫的题目说给的是连通图,第一个事例就是不联通的,然后我发现在度为0的连通分量上添加一条边

    到任意连通分量不会改变原先连通分量度为1的个数,还是能利用(leaf+1)/2这一结论。

    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    #include <stack>
    #define N 10010
    using namespace std;
    struct node
    {
        int x,y,w,next,flag;
    } eg[2*N];
    stack<int>q;
    int tt,head[N],dfn[N],low[N],ti,n,m,be[N],bridge[N][2],tp;
    int cnt,de[N];
    void init()
    {
        tt=0;
        ti=0;
        tp=0;
        cnt=0;
        memset(head,-1,sizeof(head));
        memset(dfn,0,sizeof(dfn));
        memset(be,0,sizeof(be));
        memset(de,0,sizeof(de));
        memset(bridge,0,sizeof(bridge));
        while(!q.empty()) q.pop();
    }
    void add(int xx,int yy)
    {
        eg[tt].x=xx;
        eg[tt].y=yy;
        eg[tt].next=head[xx];
        head[xx]=tt++;
    }
    void tarjan(int u,int fa)
    {
        dfn[u]=low[u]=++ti;
        q.push(u);
        for(int i=head[u]; i!=-1; i=eg[i].next)
        {
            int v=eg[i].y;
            if(v==fa) continue;
            if(!dfn[v])
            {
                tarjan(v,u);
                low[u]=min(low[u],low[v]);
                if(low[v]>dfn[u])//边(u,v)为桥,可以统计一个边连通分支
                {
                    bridge[tp][0]=u;
                    bridge[tp++][1]=v;
                    ++cnt;
                    int w;
                    do
                    {
                        w=q.top();
                        q.pop();
                        be[w]=cnt;
                    }
                    while(w!=v);//注意点u并没有出栈,因为点u属于另一个边连通分量
                }
            }
            else
            {
                low[u]=min(dfn[v],low[u]);
            }
        }
    }
    int main()
    {
        int xx[10001],yy[10001];
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&m);
            init();
            for(int i=0; i<m; i++)
            {
                scanf("%d%d",&xx[i],&yy[i]);
                add(xx[i],yy[i]);
                add(yy[i],xx[i]);
            }
            for(int i=1; i<=n; i++)
            {
                if(!dfn[i])
                {
                    tarjan(i,-1);
                    if(!q.empty())
                    {
                        ++cnt;
                        int w;
                        do
                        {
                            w=q.top();
                            q.pop();
                            be[w]=cnt;
                        }
                        while(w!=i);
                    }
                }
            }
            for(int i=0; i<tp; i++)
            {
                int u=bridge[i][0];
                int v=bridge[i][1];
                de[be[u]]++;
                de[be[v]]++; //统计缩点后的的度
            }
            int du[N];
            int sum=0;
            memset(du,0,sizeof(du));
            for(int i=0; i<m; i++)
            {
                if(be[xx[i]]!=be[yy[i]])
                {
                    du[be[xx[i]]]++;
                    du[be[yy[i]]]++;
                }
            }
            if(cnt==1)
            {
                printf("0
    ");
                continue;
            }
            for(int i=1; i<=cnt; i++)
            {
                if(du[i]==0)
                    sum++;
            }
            // printf("sum==%d
    ",sum);
            /* for(int i=0;i<tp;i++)
             {
                 printf("bridge==%d %d
    ",bridge[i][0],bridge[i][1]);
             }
             printf("cnt==%d
    ",cnt);*/
            int leaf=0;
            for(int i=1; i<=cnt; i++)
            {
                if(de[i]==1)
                    leaf++;
            }
            // printf("leaf==%d
    ",leaf);
            printf("%d
    ",(leaf+1)/2+sum);
        }
        return 0;
    }

    poj3352:贴一下大牛的解法,我只会第二种。

    双连通分量

    题意:比较裸的题意,就是给一个无向图,问添加多少条边后能使整个图变成双连通分量

    分析:建议先学了双连通分量的相关知识,因为这题是算是个模板题(我自己写了模板,过了这题,但是还没有充分测试),如果没学好相关知识即便这个模板题也不好懂

    双连通分量分为【点双连通分量,边双连通分量】,这题是个边双连通分量,就是要求出整个图的边双连通分量,然后缩点,然后找出缩点后每个点的度,度为1的点其实是树叶,答案就是(leaf+1)/2去上整,为什么是这个答案,网上的解释是,每次找到两个叶子他们的最近公共祖先最远,然后给这两个叶子连一条边,然后依次找出这样的点,所以是(leaf+1)/2

    现在为问题就是1.怎么缩点。2.怎么统计缩点后的度

     ////////////  注意一点  ///////////////////

    这题的题意是保证了图是连通的,所以才有上面的计算公式,所以只要dfs一次,如果图不连通,可能要dfs多次,并且不是上面的计算公式(leaf+1)/2

    两种做法:

    1.简化tarjan,在边双连通分量中,每个点low[u]其实已经记录是这个点u是属于哪个边双连通分量了,low[u] = low[v] ,那么点u和点v在一个边双连通分量中,所以我们可以不用急着找什么连通分量,我们先运行一次dfs,把那个顶点的low都计算出来,然后我们查看原图的每一条边(u,v),看看原图的两个点u,v是不是属于不同的连通分量,是的话,缩点后它们之间就有一条边,那么就要统计它们的度。统计完后就可以知道哪些是叶子了

    2.上面的做法,是在dfs后再处理边双连通分量的,可以一边dfs,一边就找到边双连通分量呢?是可以,这个方法才是要讲的重点

    首先有几个知识点

    先搞清楚,树边,后向边,前向边,横叉边是什么,维基百科有讲解

    对原图缩点后,变成了一棵无根树,树的边是什么?其实就是原图的桥,所以,我们可以在dfs过程中把所有的桥保存下来,放在一个表中,然后dfs完后,直接去查看那个表,桥的两端是两个点,这两个点是一个属于两个不同的边双连通分量的,所以我们可以直接统计这些缩点的度

    判断桥的条件比较简单,对于一条树边(注意是树边),在dfs过程中是从u到v的(可以看做u是v的父亲),且满足low[v] > dfn[u] , 那么无向边(u,v)就是桥,就可以把这条边保存在表中

    另外在这个dfs中借助了栈(方法1可以用栈,也可以不用,因为方法1简化了tarjan),在dfs过程中访问了点就不断入栈。在找到一条桥后,就准备将一些点出栈,因为这些准备出栈的点都是属于一个边双连通分量的,出栈的终于条件是,点v最后出来,点u不能出,注意,点u不能,点u不是属于点v的那个连通分量的,因为桥(u,v)分开了他们

    方法1:简化了tarjan的过程,注意vis的意思,其实它的作用代替了栈的作用,vis[i]=0,1,2分别表示还没访问,已经访问但是还没退出,访问完并退出,它表示的是一种时间上的顺序

    这个代码跑得快,0ms

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    #define N 1010
    #define M 1010
    #define INF 0x3f3f3f3f
    
    int n,tot;
    int head[N];
    struct edge
    {
        int u,v,next;
    }e[2*M];
    int dfn[N],low[N],vis[N],dcnt,bcnt,de[N];
    
    inline int min(int x , int y)
    {
        return x<y ? x:y;
    }
    
    void add(int u ,int v ,int k)
    {
        e[k].u = u; e[k].v = v;
        e[k].next = head[u]; head[u] = k++;
        u = u^v; v = u^v; u = u^v;
        e[k].u = u; e[k].v = v;
        e[k].next = head[u]; head[u] = k++;
    }
    
    void dfs(int u ,int fa)
    {
        dfn[u] = low[u] = ++dcnt;
        vis[u] = 1;
        for(int k=head[u]; k!=-1; k=e[k].next)
        {
            int v = e[k].v;
            if(v == fa) continue;
            if(!vis[v]) //树边
            {
                dfs(v,u);
                low[u] = min(low[u] , low[v]);
            }
            else if(vis[v] == 1) //后向边
                low[u] = min(low[u] , dfn[v]);
            //如果是横叉边为vis[v] == 2 , 跳过
        }
        vis[u] = 2;
    }
    
    void solve()
    {
        memset(dfn,0,sizeof(dfn));
        memset(de,0,sizeof(de));
        memset(vis,0,sizeof(vis));
        dcnt = bcnt = 0;
        for(int i=1; i<=n; i++)
            if(!vis[i])
                dfs(i,i);
        for(int u=1; u<=n; u++)
            for(int k=head[u]; k!=-1; k=e[k].next)
            {
                int v = e[k].v;
                if(low[u] != low[v]) //属于不同的边连通分量
                    de[low[u]]++;
            }
        int leaf = 0;
        for(int i=1; i<=n; i++)
            if(de[i] == 1)
                leaf++;
        cout << (leaf+1)/2 << endl;
    }
    
    int main()
    {
        while(cin>> n >> tot)
        {
            int u,v,k = 0;
            memset(head,-1,sizeof(head));
            for(int i=0; i<tot; i++,k+=2)
            {
                cin >> u >> v;
                add(u,v,k);
            }
            solve();
        }
        return 0;
    }

    方法2:这个代码慢啊,150ms,而且不知道模板还有没有其他的问题,至少是还没能处理重边

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    #define N 1010
    #define M 1010
    #define min(a,b) ((a)<(b)?(a):(b))
    
    int n,tot;
    int head[N],dfn[N],low[N],belong[N],de[N],stack[N],bridge[M][2],ins[N],dcnt,bcnt,top,bnum;
    struct edge
    {
        int u,v,next;
    }e[2*M];
    
    void add(int u ,int v ,int k)
    {
        e[k].u = u; e[k].v = v; e[k].next = head[u]; head[u] = k++;
        u = u^v;  v = u^v;  u = u^v;
        e[k].u = u; e[k].v = v; e[k].next = head[u]; head[u] = k++;
    }
    
    void dfs(int u ,int fa)
    {
        dfn[u] = low[u] = ++dcnt;
        stack[++top] = u; ins[u] = 1;
        for(int k=head[u]; k!=-1; k=e[k].next)
        {
            int v = e[k].v;
            if(v == fa) continue;
            if(!dfn[v]) //树边
            {
                dfs(v,u);
                low[u] = min(low[u] , low[v]);
                if(low[v] > dfn[u]) //边(u,v)为桥,可以统计一个边连通分支
                {
                    //保存桥
                    bridge[bnum][0] = u;
                    bridge[bnum++][1] = v;
                    
                    ++bcnt;
                    while(true)
                    {
                        int x = stack[top--];
                        ins[x] = 0;
                        belong[x] = bcnt;
                        if(x == v) break;
                    }//注意点u并没有出栈,因为点u属于另一个边连通分量
                }
            }
            else if(ins[v]) //后向边
                low[u] = min(low[u] , dfn[v]);
            //横叉边为(dfn[v] && !ins[v]),跳过
        }
    }
    
    void solve()
    {
        memset(dfn,0,sizeof(dfn));
        memset(de,0,sizeof(de));
        memset(ins,0,sizeof(ins));
        dcnt = bcnt = top = bnum = 0;
        dfs(1,-1);
        if(top)
        {
            ++bcnt;
            while(true)
            {
                int x = stack[top--];
                ins[x] = 0;
                belong[x] = bcnt;
                if(x == 1) break;
            }
        }
    
        for(int i=0; i<bnum; i++) //取出所有的桥
        {
            int u = bridge[i][0];
            int v = bridge[i][1];
            de[belong[u]]++;
            de[belong[v]]++;
            //统计缩点后的的度
        }
        int leaf = 0;
        for(int i=1; i<=bcnt; i++)
            if(de[i] == 1)
                leaf++;
        cout << (leaf+1)/2 << endl;
        
    //可以把下面的注释去掉,看看记录的内容,帮组理解
    /*
        for(int u=1; u<=n; u++)
            cout << u << "[" << belong[u] << "]" << endl;
        for(int i=1; i<=bcnt; i++)
            cout << "[" << de[i] << "]" << endl;
        for(int i=0; i<bnum; i++)
        {
            int u = bridge[i][0], v = bridge[i][1];
            printf("桥: %d %d
    ",u,v);
            printf("缩点后的边: %d %d
    ",belong[u] , belong[v]);
        }
    */
    }
    
    int main()
    {
        while(cin >> n >> tot)
        {
            memset(head,-1,sizeof(head));
            int u,v,k=0;
            for(int i=0; i<tot; i++,k+=2)
            {
                cin >> u >> v;
                add(u,v,k);
            }
            solve();
        }
        return 0;
    }
    View Code
  • 相关阅读:
    (转载)Rime输入法—鼠须管(Squirrel)词库添加及配置
    (转载)Windows下小狼毫输入法(Rime)的安装与配置(含导入搜狗词库)
    (转载)WinCC 卸载后 Simatic Shell 的删除
    (转载)西门子PLC学习笔记十五-(数据块及数据访问方式)
    (转载)一张表搞清楚西门子S7系列标准DB块与优化DB块
    (转载)Navicat Premium 12.1.16.0安装与激活
    (转载)MySQl数据库-批量添加数据的两种方法
    (转载)用C#实现MySQL建库及建表
    设置MYSQL数据库编码为UTF-8
    [设计模式]工厂方法模式(Factory Method)
  • 原文地址:https://www.cnblogs.com/zhangmingcheng/p/4234808.html
Copyright © 2011-2022 走看看