HDU 5409 CRB and Graph (边双连通+DFS)

<题目链接>

题目大意:

  给你一个连通的无向图,问你删除每一条边后,是否能够出现一对(u,v),使得u,v不连通,且u<v,如果有多对u,v,则输出尽量大的u,和尽量小的v。

解题分析:
  首先要明确,因为该图是连通的无向图,所以删除的边是桥才能够使至少两点不连通。但是对于删除桥的情况,如何输出尽可能大的u和尽可能小的v呢?

  我们要知道,删除一个桥,是将整张图分成两部分,这两部分的点仍然是连通的,并且,由于题目要求u<v,且u尽可能的大,v尽可能的小,所以,我们可以推断出,u与n一定不在同一部分,并且u是它所属部分的最大值,同时由于所有点的序号是连续的,u是那一部分的最大值,所以v的值一定是u+1。所以本题的目标就已经很明确了,就是先对每个点双连通分量进行缩点,并且维护每个连通分量的最大值。最后寻找u值得时候,就直接用DFS寻找不含n点的部分的最大值即可。

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <algorithm>
  4 using namespace std;
  5 const int MAXN = 1e5+10;
  6 #define clr(a,b) memset(a,b,sizeof(a))
  7 struct Edge{
  8     int to,next;
  9     bool vis,bridge;
 10     Edge(){}
 11     Edge(int _to,int _next):to(_to),next(_next){vis=false;bridge=false;}
 12 }e[MAXN<<1];
 13 int times,n,m,top,bcc,tot;
 14 int dfn[MAXN],low[MAXN],pre[MAXN],head[MAXN],stk[MAXN],belong[MAXN],res[MAXN];
 15 void init(){
 16     top=times=bcc=tot=0;
 17     clr(head,-1);clr(dfn,0);clr(low,0);clr(belong,0);clr(pre,0);
 18 }
 19 void addedge(int u,int v){
 20     e[tot]=Edge(v,head[u]);head[u]=tot++;    
 21     e[tot]=Edge(u,head[v]);head[v]=tot++;
 22 }
 23 void Tarjan(int u){
 24     dfn[u]=low[u]=++times;
 25     stk[++top]=u;
 26     for(int i=head[u];i!=-1;i=e[i].next){
 27         int v=e[i].to;
 28         if(e[i].vis)continue;
 29         e[i].vis=e[i^1].vis=true;     //将正反边全部标记
 30         if(!dfn[v]){
 31             pre[v]=u;      //记录下父亲节点
 32             Tarjan(v);
 33             low[u]=min(low[u],low[v]);
 34         }
 35         else if(!belong[v])low[u]=min(low[u],dfn[v]);
 36     }
 37     if(dfn[u]==low[u]){
 38         bcc++;
 39         res[bcc]=-1;     //进行初始化
 40         belong[u]=bcc;  
 41         while(true){
 42             int v = stk[top--];
 43             belong[v]=bcc;
 44             res[bcc]=max(res[bcc],v);     //维护连通分量中的最大值
 45             if(v==u)break;
 46         }
 47     }
 48 }
 49 Edge edg[MAXN*2];
 50 int dep[MAXN],val[MAXN],head1[MAXN],tot1;
 51 void init1(){
 52     tot1=0;
 53     clr(head1,-1);clr(dep,0);
 54 }
 55 void AddEdge(int u,int v){
 56     edg[tot1]=Edge(v,head1[u]);head1[u]=tot1++;
 57     edg[tot1]=Edge(u,head1[v]);head1[v]=tot1++;
 58 }
 59 void dfs(int u,int d){    //得到以u为根的子树中编号最大的节点,并且得到每个节点的深度
 60     dep[u]=d;
 61     val[u]=res[u];
 62     for(int i=head1[u];~i;i=edg[i].next){
 63         int v=edg[i].to;
 64         if(!dep[v]){
 65             dfs(v,d+1);
 66             val[u]=max(val[u],val[v]);    //val[u]为以u为根的子树中最大的编号
 67         }
 68     }
 69 }
 70 void solve(){
 71     Tarjan(1);
 72     for(int i=0;i<tot;i++){     //遍历所有的边
 73         if(e[i].bridge)continue;
 74         int u=e[i].to,v=e[i^1].to;
 75         if(pre[v]==u&&dfn[u]<low[v]){    //如果u为v的父亲,且dfn[u]<low[v],说明u、v之间为桥
 76             e[i].bridge=e[i^1].bridge=true;  //标记该边是否为桥
 77         } 
 78     }
 79     init1();     //为缩点后重新构图进行初始化
 80     for(int i=0;i<tot;i+=2){
 81         int u=e[i].to,v=e[i^1].to;
 82         if(belong[u]!=belong[v]){
 83             AddEdge(belong[u],belong[v]);     //缩点后进行重新构图
 84         }
 85     }
 86     dfs(belong[n],1);    //以包含n的点双连通分量为树根
 87     for(int i=0;i<tot;i+=2){
 88         int u=e[i].to,v=e[i^1].to;
 89         if(e[i].bridge){   //因为肯定是两边中深度更浅的节点为根的子树中包含n节点,所以直接输出以深度更深的节点为根的最大值和这个最大值+1(这个最大值相当于是u,因为v>u,并且v要最小所以v为u+1)
 90             if(dep[belong[u]]>dep[belong[v]])printf("%d %d
",val[belong[u]],val[belong[u]]+1);
 91             else printf("%d %d
",val[belong[v]],val[belong[v]]+1);
 92         }
 93         else printf("0 0
");     //如果不是桥,则输出0 0 
 94     }
 95 }
 96 int main(){
 97     int T;scanf("%d",&T);
 98     while(T--){
 99         scanf("%d%d",&n,&m);
100         init();
101         for(int i=0,x,y;i<m;i++){
102             scanf("%d%d",&x,&y);
103             addedge(x,y);
104         }
105         solve();
106     }
107     return 0;
108 }

2018-12-04

原文地址:https://www.cnblogs.com/00isok/p/10065216.html