[hihoCoder] 第五十二周: 连通性·一

题目1 : 连通性·一

时间限制: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

小Ho:这次的问题好简单!我只要依次删除每一个节点,每一条边,然后用DFS判断一下连通性就好了。

小Hi:没那么简单啦,好好看清楚N和M的范围啦。

小Ho:@[email protected],N和M怎么这么大,那应该怎么办?

小Hi:其实也很容易啊。这次我们要用到的叫做Tarjan算法。首先我给你普及一点点基本的知识:

割边:在连通图中,删除了连通图的某条边后,图不再连通。这样的边被称为割边,也叫做桥。

割点:在连通图中,删除了连通图的某个点以及与这个点相连的边后,图不再连通。这样的点被称为割点。

DFS搜索树:用DFS对图进行遍历时,按照遍历次序的不同,我们可以得到一棵DFS搜索树。在上面例子中,得到的搜索树为:

树边:在搜索树中的蓝色线所示,可理解为在DFS过程中访问未访问节点时所经过的边,也称为父子边

回边:在搜索树中的橙色线所示,可理解为在DFS过程中遇到已访问节点时所经过的边,也称为返祖边、后向边

观察DFS搜索树,我们可以发现有两类节点可以成为割点:

  • 对根节点u,若其有两棵或两棵以上的子树,则该根结点u为割点;
  • 对非叶子节点u(非根节点),若其子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与u的子树的节点不再连通;则节点u为割点。

对于根结点,显然很好处理;但是对于非叶子节点,怎么去判断有没有回边是一个值得深思的问题。

我们用dfn[u]记录节点u在DFS过程中被遍历到的次序号,low[u]记录节点u或u的子树通过非父子边追溯到最早的祖先节点(即DFS次序号最小),那么low[u]的计算过程如下:

对于给的例子,其求出的dfn和low数组为:

id  1 2 3 4 5 6
dfn 1 2 3 4 5 6
low 1 1 1 4 4 4
		

可以发现,对于情况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]);
		}
	}
}
		

小Ho:我大概明白了,如果这样来做,时间复杂度应该也很低吧。

小Hi:没错,它的时间复杂度是O(n+m)的,非常快。

小Ho:好!看我来实现它!

注意可能有重边,另外要注意割点要去重!

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3
 4 int N, M;
 5 int u, v;
 6 vector<vector<int>> graph;
 7 vector<int> dfn, low, parent;
 8 vector<bool> visit;
 9 set<int> art;
10 set<pair<int, int>> brg;
11 set<pair<int, int>> st1, st2;
12
13 void dfs(int u) {
14     static int counter = 0;
15     int children = 0;
16     visit[u] = true;
17     dfn[u] = low[u] = ++counter;
18     for (auto v : graph[u]) {
19         if (!visit[v]) {
20             ++children;
21             parent[v] = u;
22             dfs(v);
23             low[u] = min(low[u], low[v]);
24             if (parent[u] == 0 && children > 1) {
25                 art.insert(u);
26             }
27             if (parent[u] != 0 && low[v] >= dfn[u]) {
28                 art.insert(u);
29             }
30             if (low[v] > dfn[u]) {
31                 brg.insert({min(u, v), max(u, v)});
32             }
33         } else if (v != parent[u]) {
34             low[u] = min(low[u], dfn[v]);
35         }
36     }
37 }
38
39 void solve() {
40     dfs(1);
41     if (art.empty()) {
42         cout << "Null" << endl;
43     } else {
44         for (auto a : art) cout << a << " ";
45         cout << endl;
46     }
47     if (!brg.empty()) {
48         for (auto b : brg) if (st2.find(b) == st2.end()) {
49             cout << b.first << " " << b.second << endl;
50         }
51     }
52 }
53
54 int main() {
55     while (cin >> N >> M) {
56         graph.assign(N + 1, vector<int>(0));
57         dfn.assign(N + 1, 0);
58         low.assign(N + 1, 0);
59         parent.assign(N + 1, 0);
60         visit.assign(N + 1, false);
61         st1.clear(), st2.clear();
62         art.clear(), brg.clear();
63         for (int i = 0; i < M; ++i) {
64             cin >> u >> v;
65             graph[u].push_back(v);
66             graph[v].push_back(u);
67             if (st1.find({min(u, v), max(u, v)}) != st1.end()) {
68                 st2.insert({min(u, v), max(u, v)});
69             }
70             st1.insert({min(u, v), max(u, v)});
71         }
72         solve();
73     }
74     return 0;
75 }
时间: 2025-01-18 05:44:48

[hihoCoder] 第五十二周: 连通性·一的相关文章

信息安全系统设计基础第十二周学习总结

第十二周代码学习 一.environ.c #include <stdio.h> #include <stdlib.h> int main(void) { printf("PATH=%s\n", getenv("PATH")); setenv("PATH", "hello", 1); printf("PATH=%s\n", getenv("PATH")); #if

20145311 《信息安全系统设计基础》第十二周学习总结

20145311 <信息安全系统设计基础>第十二周学习总结 教材学习内容总结 20145311 <信息安全系统设计基础>第十一周学习总结 20145311 <信息安全系统设计基础>第十周学习总结 20145311 <信息安全系统设计基础>第九周学习总结 视频内容总结 指针数组:即用于存储指针的数组,也就是数组元素都是指针  int *a[10] 数组指针:即指向数组的指针,指针指向一个类型和元素个数都固定的数组 int (*a)[10] 指针函数:即返回值是

2017-2018-1 20155310 《信息安全系统设计基础》第十二周学习总结

2017-2018-1 20155310 <信息安全系统设计基础>第十二周学习总结 第一周 http://www.cnblogs.com/m20155310/p/7587317.html 第二周 http://www.cnblogs.com/m20155310/p/7618222.html 第三周 http://www.cnblogs.com/m20155310/p/7672424.html 第四周 http://www.cnblogs.com/m20155310/p/7672547.html

2017-2018-1 《Linux内核原理与设计》第十二周作业

<linux内核原理与设计>第十二周作业 Sql注入基础原理介绍 分组: 和20179215袁琳完成实验 一.实验说明 ??SQL注入攻击通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,本章课程通过 LAMP 搭建 Sql 注入环境,两个实验分别介绍 Sql 注入爆破数据库.Sql 注入绕过验证两个知识点. 首先通过下面命令将代码下载到实验楼环境中,作为参照对比进行学习. $ wget http://labfil

QT开发(五十二)———QML语言

QT开发(五十二)---QML语言 QML是一种声明语言,用于描述程序界面.QML将用户界面分解成一块块小的元素,每一元素都由很多组件构成.QML定义了用户界面元素的外观和行为:更复杂的逻辑则可以结合JavaScript脚本实现. 一.QML基础语法 1.Import语句 QML代码中,import语句一般写在头几行,主要用途如下:     A.包含类型的全名空间     B.包含QML代码文件的目录     C.JavaScript代码文件 格式如下: import Namespace Ver

第十二周进度条

第十二周          日期  星期一   星期二   星期三   星期四   星期五   星期六   星期日  了解到的知识点 js获取当前时间 var d = new Date() var nowYear = +d.getFullYear() EF框架填充下拉菜单 var model = db.GYSAllFoods.Select(m => new{GYS = m.GYS}).Distinct();//去重很关键            foreach (var item in model

学习进度第十二周

  第十二周 所花时间(包括上课) 11h(4h上课,7课下) 代码量(行) 220 博客量(篇) 1 了解到的知识点 这个星期主要进行了安卓实验和大作业的编写,从中学到了利用安卓SQLite 数据库 进行表的存储以及应用,按照教程成功编写了一个特别小的视频播放器,学会了进度 条等控件的使用.

学习进度-第十二周

  第十二周 所花时间(包括上课) 10小时 代码量(行) 48行 博客量(篇) 3篇 了解到的知识点

第十二周(补)

这几周有点心不在焉的,学习状态不好,作业都忘记写了,有的是存在记事本里忘记上传 周次 学习时间 新编写代码行数 博客量(数) 学到知识点 第十二周 6 80 1 html                               <html><head> <title> HTML</title></head><body > <h1>会员注册界面</h1><form action="proces