POJ 3177 Redundant Paths(边双连通分量+缩点)


大致题意:

       为了保护放牧环境,避免牲畜过度啃咬同一个地方的草皮,牧场主决定利用不断迁移牲畜进行喂养的方法去保护牧草。然而牲畜在迁移过程中也会啃食路上的牧草,所以如果每次迁移都用同一条道路,那么该条道路同样会被啃咬过度而遭受破坏。

       现在牧场主拥有F个农场,已知这些农场至少有一条路径连接起来(不一定是直接相连),但从某些农场去另外一些农场,至少有一条路可通行。为了保护道路上的牧草,农场主希望再建造若干条道路,使得每次迁移牲畜时,至少有2种迁移途径,避免重复走上次迁移的道路。已知当前有的R条道路,问农场主至少要新建造几条道路,才能满足要求?


知识汇总:
求桥:
在求割点的基础上吗,假如一个边没有重边(重边 1-2, 1->2 有两次,那么 1->2 就是有两条边了,那么 1->2就不算是桥了)。
当且仅当 (u,v) 为父子边,且满足 dfn[u] < low[v]

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

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

在此注解一下:双连通分量判断是以弹栈为标准的……

   注意一点  ///

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

ps:去重还是哈希一下比较好

代码:

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1e3 + 5;
int low[maxn], dfn[maxn], id[maxn], bcc_cnt, dfs_cnt;
int deg[maxn], top, Stack[maxn], n, m;
vector v[maxn];
stack s;
void init()
{
    memset(low, 0, sizeof(low));
    memset(id, 0, sizeof(id));
    memset(dfn, 0, sizeof(dfn));
    memset(deg, 0, sizeof(deg));
    bcc_cnt = dfs_cnt = top = 0;
    for(int i = 0; i < maxn; i++)
        v[i].clear();
}
void tarjan(int x, int f)
{
    dfn[x] = low[x] = ++dfs_cnt;
    Stack[top++] = x;
    int k = 0;
    for(int i = 0; i < v[x].size(); i++)
    {
        int to = v[x][i];
        if(to == f && !k) //处理重边
        {
            k++;
            continue;
        }
        if(!dfn[to])
        {
            tarjan(to, x);
            low[x] = min(low[x], low[to]);
        }
        else if(!id[to])
            low[x] = min(low[x], dfn[to]);
    }
    if(low[x] == dfn[x])
    {
        bcc_cnt++;
        while(top > 0)
        {
            int u = Stack[--top];
            id[u] = bcc_cnt;
            if(u == x) break;
        }
    }
}
void bcc()
{
//    for(int i = 1; i <= n ; i++)
//        if(!dfn[i])
//            tarjan(i);
    tarjan(1, -1); //因为图是联通的,只要DFS一个点,全图都可以搜到
}
int main()
{
    while(~scanf("%d%d", &n, &m))
    {
        init();
        int x, y;
        for(int i = 0; i < m; i++)
        {
            scanf("%d%d", &x, &y);
            v[x].push_back(y);
            v[y].push_back(x);
        }
        bcc();
        for(int i = 1; i <= n; i++)
        {
            for(int j = 0; j < v[i].size(); j++)
            {
                int to = v[i][j];
                if(id[to] != id[i])
                {
                    deg[id[i]]++;
                    deg[id[to]]++;
                }
            }
        }
        int ans = 0;
        for(int i = 1; i <= bcc_cnt; i++)
        {
            if(deg[i] == 2) ans++;
        }
        printf("%d\n",(ans+1)/2);
    }
    return 0;
}

kuangbin代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAXN = 5010;//点数
const int MAXM = 20010;//边数,因为是无向图,所以这个值要*2

struct Edge
{
    int to,next;
    bool cut;//是否是桥标记
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~block
int Index,top;
int block;//边双连通块数
bool Instack[MAXN];
int bridge;//桥的数目

void addedge(int u,int v)
{
    edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut=false;
    head[u] = tot++;
}

void Tarjan(int u,int pre)
{
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    for(int i = head[u];i != -1;i = edge[i].next)
    {
        v = edge[i].to;
        if(v == pre)continue;
        if( !DFN[v] )
        {
            Tarjan(v,u);
            if( Low[u] > Low[v] )Low[u] = Low[v];
            if(Low[v] > DFN[u])
            {
                bridge++;
                edge[i].cut = true;
                edge[i^1].cut = true;
            }
        }
        else if( Instack[v] && Low[u] > DFN[v] )
            Low[u] = DFN[v];
    }
    if(Low[u] == DFN[u])
    {
        block++;
        do
        {
            v = Stack[--top];
            Instack[v] = false;
            Belong[v] = block;
        }
        while( v!=u );
    }
}
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}

int du[MAXN];//缩点后形成树,每个点的度数
void solve(int n)
{
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
    Index = top = block = 0;
    Tarjan(1,0);
    int ans = 0;
    memset(du,0,sizeof(du));
    for(int i = 1;i <= n;i++)
       for(int j = head[i];j != -1;j = edge[j].next)
          if(edge[j].cut)
             du[Belong[i]]++;
    for(int i = 1;i <= block;i++)
       if(du[i]==1)
          ans++;
    //找叶子结点的个数ans,构造边双连通图需要加边(ans+1)/2
    printf("%d\n",(ans+1)/2);
}
int main()
{
    int n,m;
    int u,v;
    while(scanf("%d%d",&n,&m)==2)
    {
        init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        solve(n);
    }
    return 0;
}


有向图的的情况比较简单只有一种强连通,重边和连向自己的边对于强连通都没有任何影响

无向图的双连通要分点双连通(biconnected)和边双连通(edge_biconnected),连向自己的边对于俩种双连通也没有任何影响,但是重边对点双连通没有影响,但是对于边双连通有影响,因为在求边双连通时,要求对于任意俩点至少存在两条“边不重复”的路径,所以这个时候表示图我们不能用vector了,而是用邻接表,添加边的时候我们要一次添加正反俩条边,而且要相互可以索引查找,类似网络流里的反向弧,这样在我们dfs求割边时要以边的下标作为标记,在访问一了一条边时,要把这条边和其反向边同时标记为访问,最后对所有的边进行遍历,发现low[e.v] < pre[e.u]时,同样要把正反俩条边标记成割边,最后在不经过桥的情况下dfs求出边双连通分量即可


[cpp]  view plain  copy
  1. struct EDGE  
  2. {  
  3.     int u, v;  
  4.     int next;  
  5. };  
  6.   
  7. int first[MAXN], rear;  
  8. EDGE edge[MAXE];  
  9.   
  10. void init(int n)  
  11. {  
  12.     memset(first, -1, sizeof(first[0])*(n+1));  
  13.     rear = 0;  
  14. }  
  15.   
  16. void insert(int tu, int tv, int tw)  
  17. {  
  18.     edge[rear].u = tu;  
  19.     edge[rear].v = tv;  
  20.     edge[rear].next = first[tu];  
  21.     first[tu] = rear++;  
  22.     edge[rear].u = tv;  
  23.     edge[rear].v = tu;  
  24.     edge[rear].next = first[tv];  
  25.     first[tv] = rear++;  
  26. }  
  27.   
  28. struct FIND_BRIDGE  
  29. {  
  30.     int pre[MAXN], low[MAXN];  
  31.     bool vis_e[MAXE];      //是否访问了边  
  32.     bool is_bridge[MAXE];  //是否是桥  
  33.     int dfs_clock;  
  34.   
  35.     void dfs(int cur)  
  36.     {  
  37.         pre[cur] = low[cur] = ++dfs_clock;  
  38.         for(int i = first[cur]; ~i; i = edge[i].next)  
  39.         {  
  40.             int tv = edge[i].v;  
  41.             if(!pre[tv])  
  42.             {  
  43.                 vis_e[i] = vis_e[i^1] = true;  
  44.                 dfs(tv);  
  45.                 low[cur] = min(low[cur], low[tv]);  
  46.                 if(pre[cur] < low[tv]) is_bridge[i] = is_bridge[i^1] = true;  
  47.             }  
  48.             else  
  49.                 if(pre[tv] < pre[cur] && !vis_e[i])  
  50.                 {  
  51.                     vis_e[i] = vis_e[i^1] = true;  
  52.                     low[cur] = min(low[cur], pre[tv]);  
  53.                 }  
  54.         }  
  55.     }  
  56.   
  57.     void find_bridge(int n)  
  58.     {  
  59.         dfs_clock = 0;  
  60.         memset(pre, 0, sizeof(pre[0])*(n+1));  
  61.         memset(vis_e, 0, sizeof(vis_e[0])*rear);  
  62.         memset(is_bridge, 0, sizeof(is_bridge[0])*rear);  
  63.         for(int i = 1; i <= n; ++i)  
  64.             if(!pre[i])  
  65.                 dfs(i);  
  66.     }  
  67. } fb;  
  68. //接着在不经过桥的情况下dfs求出所有双强连通分量即可  




你可能感兴趣的:(图论,ACM)