<题目链接>
题目大意:
给你一个连通的无向图,问你删除每一条边后,是否能够出现一对(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