时间限制:
10000ms
单点时限:
1000ms
内存限制:
256MB
-
6 7 1 2 1 3 2 3 3 4 4 5 4 6 5 6
样例输出
-
3 4 3 4
描述
还记得上次小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小的排在前面。若没有满足要求的边,则不输出
提示:割边&割点
割边:在连通图中,删除了连通图的某条边后,图不再连通。这样的边被称为割边,也叫做桥。
割点:在连通图中,删除了连通图的某个点以及与这个点相连的边后,图不再连通。这样的点被称为割点。
DFS搜索树:用DFS对图进行遍历时,按照遍历次序的不同,我们可以得到一棵DFS搜索树。在上面例子中,得到的搜索树为:
树边:在搜索树中的蓝色线所示,可理解为在DFS过程中访问未访问节点时所经过的边,也称为父子边
回边:在搜索树中的橙色线所示,可理解为在DFS过程中遇到已访问节点时所经过的边,也称为返祖边、后向边
观察DFS搜索树,我们可以发现有两类节点可以成为割点:
-
对根节点u,若其有两棵或两棵以上的子树,则该根结点u为割点;
-
对非叶子节点u(非根节点),若其中的某棵子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与该棵子树的节点不再连通;则节点u为割点。
对于根结点,显然很好处理;但是对于非叶子节点,怎么去判断有没有回边是一个值得深思的问题。
我们用dfn[u]记录节点u在DFS过程中被遍历到的次序号,low[u]记录节点u或u的子树通过非父子边追溯到最早的祖先节点(即DFS次序号最小),那么low[u]的计算过程如下:
可以发现,对于情况2,当(u,v)为树边且low[v]≥dfn[u]时,节点u才为割点。
而当(u,v)为树边且low[v]>dfn[u]时,表示v节点只能通过该边(u,v)与u连通,那么(u,v)即为割边。
Tarjan算法的代码如下:
void dfs(int u) {
//记录dfs遍历次序
static int counter = 0;
//记录节点u的子树数
int children = 0;
ArcNode *p = graph[u].firstArc;
visit[u] = 1;
//初始化dfn与low
dfn[u] = low[u] = ++counter;
for(; p != NULL; p = p->next) {
int v = p->adjvex;
//节点v未被访问,则(u,v)为树边
if(!visit[v]) {
children++;
parent[v] = u;
dfs(v);
low[u] = min(low[u], low[v]);
//case (1)
if(parent[u] == NIL && children > 1) {
printf("articulation point: %d\n", u);
}
//case (2)
if(parent[u] != NIL && low[v] >= dfn[u]) {
printf("articulation point: %d\n", u);
}
//bridge
if(low[v] > dfn[u]) {
printf("bridge: %d %d\n", u, v);
}
}
//节点v已访问,则(u,v)为回边
else if(v != parent[u]) {
low[u] = min(low[u], dfn[v]);
}
}
}
#include <iostream>
#include <string.h>
#include <cstdio>
#include <set>
#include <algorithm>
using namespace std;
#define maxn 20100
#define maxe 200100
struct Edge
{
int v,next;
}E[maxe];
set<int> node;
set<pair<int, int> > edge;
bool vis[maxn];
int dfn[maxn],low[maxn],parent[maxn],head[maxn],counter,len,n, m;
void AddEdge(int a, int b)
{
len++;
E[len].v = b;
E[len].next = head[a];
head[a] = len;
}
void dfs(int u)
{
int child = 0;
vis[u] = true;
dfn[u] = ++counter;
low[u] = counter;
for (int i = head[u]; i!=-1; i = E[i].next)
{
int v = E[i].v;
if (!vis[v])
{
child++;
parent[v] = u;
dfs(v);
low[u] = min(low[u], low[v]);
if(parent[u]==-1 && child > 1)
{
node.insert(u); //割点 可能会被执行多次
}
if(parent[u]!=-1 && low[v] >= dfn[u])
{
node.insert(u); //割点 可能会被执行多次
}
if(low[v] > dfn[u])
{
int a1 = u, b1 = v;
if (a1 > b1)
swap(a1, b1);
edge.insert(make_pair(a1, b1)); //割边(桥)
}
}
else if(v != parent[u])
{
low[u] = min(low[u], dfn[v]);
}
}
}
int main()
{
int a,b;
while (scanf("%d%d", &n, &m)!=EOF)
{
memset(head, -1, sizeof(head));
for(int i=0;i<=n;i++)
{
parent[i]=-1;
}
counter=0;
memset(vis, false, sizeof(vis));
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &a, &b);
AddEdge(a,b);
AddEdge(b,a);
}
dfs(1);
if(!node.size())
{
puts("Null");
}
else
{
for (set<int>::iterator it=node.begin(); it!=node.end(); ++it)
{
printf("%d ",*it);
}
printf("\n");
}
if(!edge.empty())
{
for (set<pair<int, int> >::iterator it=edge.begin();it!=edge.end();++it)
{
printf("%d %d\n",it->first, it->second);
}
}
}
return 0;
}