POJ - 3352 无向图的割和桥以及双连通分量

    双连通分量是指图中每两个点都有两条完全不同的路径可到达..也就是去掉这个图的任意一个边一个点...两两之间依然可达..  

    图论中的桥...在有向图中是两个连通分量之间唯一的边(如果有多条那么都不是桥)...在无向图中是两个双连通分量之间的唯一边...

    而割指的是割点..割点肯定是和桥的端点...但桥的端点不一是割点..如:


   (1,4),(2,4),(3,4),(4,5),(4,6),(4,7)都是桥..所有点都是桥的端点...但是只有4是割点...1,2,3,5,6,7都不是割点..

   去掉一个连通图中的一个桥或者割点..这个连通图就将成为不连通的多个连通图..

   求无向图中的桥的方法是有Tarjan发明的..Byvoid大大的讲解很给力: http://www.byvoid.com/blog/biconnect/

   无向图的Tarjan和有向图求强连通分量的Tarjan很像...注意几个不同...

    1、求双联通分量时..同样要用到栈..但栈中不是放点..而是放边....

      2、DFS时不能从 a 递归到了 b..b又马上从a来更新...所以要多加一个notpre..代表递归b时是从哪个点进去的...防止这种情况..

      3、Low相等的点在无向图中就是在一个双连通图中...这个比有向图的方便..有向图还需要用栈来维护..通过判断退栈来判断强连通分量..

   如果要求桥和割点的话..做完Tarjan..扫描所有的边,有 DFN ( 终点 ) < LOW ( 起点 ) 的边就是桥...这条边的"终点"就是割点..

   回到POJ3352...题目给出了连通的无向图..并且两点直接最多只有一条边...求的是加几条边能使得该无向图整个变成一个双连通图..

   那么先对图做无向图的Tarjan...得到每个点所在的双连通记录在Low里....可以想象一下..如果把所有的双连通分量分别缩成一个点...那么整个图就是一棵树了..如果给一棵树..要史其成为一个双连通图...那么需要加( 叶子结点数+1)/2调边...

   这道题就是在做完Tarjan找作为叶子节点的双连通分量的个数k..然后答案就是(k+1)/2...

Program:

// POJ3352 加边做双连通图
#include<iostream>
#include<stack>
#include<math.h>
#define MAXN 5001
using namespace std;
struct pp
{  
    int x,y,next;   
}line[MAXN*5];
int n,m,link[MAXN],i,p,low[MAXN],dfn[MAXN],tp[MAXN],numtp,sum[MAXN];
bool instack[MAXN],used[MAXN];
stack<int> mystack;
void trajin(int h)
{
   int k;
   instack[h]=true;
   mystack.push(h);
   low[h]=dfn[h]=++p;
   k=link[h];
   while (k)
   {
      if (!dfn[line[k].y])
      {
            trajin(line[k].y);
            low[h]=min(low[h],low[line[k].y]);     
      }else
      if (instack[line[k].y])
      {
            low[h]=min(low[h],dfn[line[k].y]);           
      }
      k=line[k].next;   
   }
   k=h;
   if (low[k]==dfn[k])
   {
        numtp++;              
        do 
        {
             k=mystack.top();
             tp[k]=numtp;
             mystack.pop();
             instack[k]=false;         
        }while ( low[k]!=dfn[k]);              
   }
   return; 
} 
void getanswer()
{
    int i;
    memset(instack,false,sizeof(instack));
    while (!mystack.empty()) mystack.pop();
    memset(tp,0,sizeof(tp));
    memset(dfn,0,sizeof(dfn));
    p=0; numtp=0;
    for (i=1;i<=n;i++) 
     if (!dfn[i]) 
       trajin(i); 
    for (i=1;i<=n;i++)
     if (!tp[i])
     {
         printf("\n");
         return;           
     }   
    memset(link,0,sizeof(link));
    memset(sum,0,sizeof(sum)); 
    for (i=1;i<=m;i++)
    if (tp[line[i].x]!=tp[line[i].y])
    {
        p++;
        sum[tp[line[i].x]]++;
    }   
    for (i=1;i<=n;i++)
     if (!sum[tp[i]]) 
       printf("%d ",i);  
    printf("\n");
    return;
}
int main()
{ 
    while (~scanf("%d%",&n))
    {
          if (!n) break;
          scanf("%d",&m);
          memset(link,0,sizeof(link));
          memset(line,0,sizeof(line));
          for (i=1;i<=m;i++)
          {
              scanf("%d%d",&line[i].x,&line[i].y);
              line[i].next=link[line[i].x];
              link[line[i].x]=i;
          }
          getanswer();
    }
    return 0;   
}





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值