hihoCoder_#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

思路:

对于连通无向图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)为桥。类似于割顶,我们称没有桥的图为边连通图。如果一个无向图是边连通的,可以把它的边定向,得到一个强连通的有向图。

代码:

/*
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define INF 1e9
typedef long long ll;
using namespace std;
const int maxn = 20000 + 5;
const int maxv = 100000 + 5;
int N,M,a,b;
vector<int>G[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 tarjan(int u,int father){
    dfn[u]=low[u]=++idx;
    int children=0;
    for(int i=0;i<(int)G[u].size();i++){
        int v=G[u][i];
        if(v==father) continue;
        if(!dfn[v]){
            children++;
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if((father==u&&children>1)||(father!=u&&low[v]>=dfn[u])){
                vertex.insert(u);//存入割点,运用set防止重复
                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(){
    for(int i=0;i<maxn;i++)
    G[i].clear();
    vertex.clear();
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    ve=0; ed=0; idx=0;
    judge=false;
    scanf("%d%d",&N,&M);
    for(int i=0;i<M;i++){
        scanf("%d%d",&a,&b);
        G[a].push_back(b);
        G[b].push_back(a);
    }
    solve();
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值