#1183 : 连通性一·割边与割点
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
还记得上次小Hi和小Ho学校被黑客攻击的事情么,那一次攻击最后造成了学校网络数据的丢失。为了避免再次出现这样的情况,学校决定对校园网络进行重新设计。
学校现在一共拥有N台服务器(编号1..N)以及M条连接,保证了任意两台服务器之间都能够通过连接直接或者间接的数据通讯。
当发生黑客攻击时,学校会立刻切断网络中的一条连接或是立刻关闭一台服务器,使得整个网络被隔离成两个独立的部分。
举个例子,对于以下的网络:
每两个点之间至少有一条路径连通,当切断边(3,4)的时候,可以发现,整个网络被隔离为{1,2,3},{4,5,6}两个部分:
若关闭服务器3,则整个网络被隔离为{1,2},{4,5,6}两个部分:
小Hi和小Ho想要知道,在学校的网络中有哪些连接和哪些点被关闭后,能够使得整个网络被隔离为两个部分。
在上面的例子中,满足条件的有边(3,4),点3和点4。
输入
第1行:2个正整数,N,M。表示点的数量N,边的数量M。1≤N≤20,000, 1≤M≤100,000
第2..M+1行:2个正整数,u,v。表示存在一条边(u,v),连接了u,v两台服务器。1≤u<v≤N
保证输入所有点之间至少有一条连通路径。
输出
第1行:若干整数,用空格隔开,表示满足要求的服务器编号。从小到大排列。若没有满足要求的点,该行输出Null
第2..k行:每行2个整数,(u,v)表示满足要求的边,u<v。所有边根据u的大小排序,u小的排在前,当u相同时,v小的排在前面。若没有满足要求的边,则不输出
样例输入
6 7 1 2 1 3 2 3 3 4 4 5 4 6 5 6
样例输出
3 4 3 4
- 分析:tarjan求无向连通图的割点与割边(桥)。
-
引理:对于连通无向图G={V,E},S={V,T}为G的一个DFS树,则结点u是G的割点当且仅当下面条件之一被满足:1. u是T的根且u至少有两个儿子
2. u不是T的根且存在u的某个儿子w,使得从w或者w的后代没有边连回u的祖先(注意,不是连回u本身)。
类似于割点,我们可以定义无向连通图的桥(bridge):如果删除一条边e后无向图G不再连通,称e为G的桥。桥的判定也不难,只需要在发现T边(u,v)时进行判断。如果v后它的后代无法连回u或者u的祖先,则删除(u, v)后u和v不连通。即:发现T边(u, v)并递归遍历v后若dfn[u]<low[v],则(u, v)为桥。类似于割顶,我们称没有桥的图为边连通图。如果一个无向图是边连通的,可以把它的边定向,得到一个强连通的有向图。
关于图的强联通分量,块,割点,桥,请点击这里:http://blog.csdn.net/shiqi_614/article/details/7833628
void tarjan(int u,int father){ //father是u的父节点 dfn[u]=low[u]=++idx; int children=0; //计算u的儿子节点个数 for(int i=0;i<graph[u].size();i++){ int v=graph[u][i]; if(v==father) continue; if(!dfn[v]){ children++; tarjan(v,u); low[u]=min(low[u],low[v]); //判断是否割点,点可能进入多次,所以用set方便 if((father==u&&children>1)||(father!=u&&low[v]>=dfn[u])){ vertex.insert(u); judge=true; } //判断是否割边(桥) if(low[v]>dfn[u]) edge[ed++]=Edge(min(u,v),max(u,v)); } else //不需判断是否在栈中, low[u]=min(low[u],dfn[v]); } }
代码清单:
#include<map> #include<set> #include<queue> #include<stack> #include<cmath> #include<cstdio> #include<string> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn = 20000 + 5; const int maxv = 100000 + 5; int N,M,a,b; vector<int>graph[maxn]; int dfn[maxn]; int low[maxn]; int ve,ed,root,idx; bool judge; set<int>vertex; struct Edge{ int u,v; Edge(){} Edge(int u,int v){ this -> u = u; this -> v = v; } }edge[maxv]; bool cmp(Edge a,Edge b){ if(a.u==b.u) return a.v<b.v; return a.u<b.u; } void init(){ for(int i=0;i<maxn;i++) graph[i].clear(); vertex.clear(); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); ve=0; ed=0; idx=0; judge=false; } void input(){ scanf("%d%d",&N,&M); for(int i=0;i<M;i++){ scanf("%d%d",&a,&b); graph[a].push_back(b); graph[b].push_back(a); } } void tarjan(int u,int father){ //father是u的父节点 dfn[u]=low[u]=++idx; int children=0; //计算u的儿子节点个数 for(int i=0;i<graph[u].size();i++){ int v=graph[u][i]; if(v==father) continue; if(!dfn[v]){ children++; tarjan(v,u); low[u]=min(low[u],low[v]); //判断是否割点,点可能进入多次,所以用set方便 if((father==u&&children>1)||(father!=u&&low[v]>=dfn[u])){ vertex.insert(u); judge=true; } //判断是否割边(桥) if(low[v]>dfn[u]) edge[ed++]=Edge(min(u,v),max(u,v)); } else //不需判断是否在栈中, low[u]=min(low[u],dfn[v]); } } void solve(){ tarjan(1,1); sort(edge,edge+ed,cmp); if(!judge) printf("Null\n"); else{ set<int>::iterator it=vertex.begin(); for(;it!=vertex.end();it++) printf("%d ",*it); printf("\n"); } for(int i=0;i<ed;i++){ printf("%d %d\n",edge[i].u,edge[i].v); } } int main(){ init(); input(); solve(); return 0; }
版权声明:本文为博主原创文章,未经博主允许不得转载。