[蓝桥杯 2013 国 C] 危险系数
题目背景
抗日战争时期,冀中平原的地道战曾发挥重要作用。
题目描述
地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。
我们来定义一个危险系数 D F ( x , y ) DF(x,y) DF(x,y):
对于两个站点 x x x 和 y ( x ≠ y ) , y(x\neq y), y(x=y), 如果能找到一个站点 z z z,当 z z z 被敌人破坏后, x x x 和 y y y 不连通,那么我们称 z z z 为关于 x , y x,y x,y 的关键点。相应的,对于任意一对站点 x x x 和 y y y,危险系数 D F ( x , y ) DF(x,y) DF(x,y) 就表示为这两点之间的关键点个数。
本题的任务是:已知网络结构,求两站点之间的危险系数。
输入格式
输入数据第一行包含 2 2 2 个整数 n ( 2 ≤ n ≤ 1000 ) n(2 \le n \le 1000) n(2≤n≤1000), m ( 0 ≤ m ≤ 2000 ) m(0 \le m \le 2000) m(0≤m≤2000),分别代表站点数,通道数。
接下来 m m m 行,每行两个整数 u , v ( 1 ≤ u , v ≤ n , u ≠ v ) u,v(1 \le u,v \le n,u\neq v) u,v(1≤u,v≤n,u=v) 代表一条通道。
最后 1 1 1 行,两个数 u , v u,v u,v,代表询问两点之间的危险系数 D F ( u , v ) DF(u,v) DF(u,v)。
输出格式
一个整数,如果询问的两点不连通则输出 − 1 -1 −1。
样例 #1
样例输入 #1
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
样例输出 #1
2
提示
时限 1 秒, 64M。蓝桥杯 2013 年第四届国赛
思路
先用Tarjan算法求割点,再用广度优先搜索求关键点。
Tarjan函数用于在图中找出所有的割点。割点是指,如果去掉这个点以及与它相连的边,会使得原本连通的图不再连通。Tarjan函数通过深度优先搜索(DFS)的方式来找出所有的割点。
对每个节点,将其深度优先搜索序号dfn和最早可回溯的节点序号low都初始化为0。如果它还未被访问过,则调用深度优先搜索。在搜索的过程中,维护一个全局的时间戳num,代表当前的搜索次序。每当访问到一个新的节点,就将其dfn和low值都设置为当前的时间戳,并将时间戳加1。
在深度优先搜索的过程中,如果遍历到一个子节点v,而这个子节点已经被访问过,那么就尝试用这个子节点的dfn值更新当前节点u的low值,即low[u] = min(low[u], dfn[v])
。这一步表示,如果存在一个从u出发的回路可以回到v,那么u就可以通过这个回路回到v。
如果遍历到一个子节点v,而这个子节点还未被访问过,那么就对v进行深度优先搜索。在搜索结束后,尝试用v的low值更新u的low值,即low[u] = min(low[u], low[v])
。这一步表示,如果v可以回到的最早的节点也可以被u到达,那么u就可以通过v回到这个节点。
在对一个节点u的所有子节点遍历结束后,检查是否存在一个子节点v,使得low[v] >= dfn[u]
。如果存在,那么u就是一个割点。
bfs函数用来判断在移除特定节点后,起点和终点是否仍然连通。通过广度优先搜索,从起点开始,遍历所有可以到达的节点。
首先清空队列和访问标记位集,然后将起点加入队列。然后进行循环,每次从队列中取出一个节点,标记为已访问,然后遍历这个节点的所有邻接节点。
对于每一个邻接节点,如果它是终点,那么就返回true,表示起点和终点是连通的。如果这个邻接节点没有被访问过,并且不是被移除的节点,那么就将它加入到队列中。如果在遍历过程中没有找到终点,那么就返回false,表示起点和终点不连通。
注意
- 若起点就是终点,危险系数就是0,直接输出0即可。
- 需要判断起点和终点是否连通。若不连通,直接输出-1即可。
- 割点不一定是关键点,有可能存在这样的情况:路径上某个点是割点,但是移除该点后,仍能通过其他路径能到达终点。需要对每个割点进行判断,若移除该割点后无法到达终点的,才是路径上的关键点。举个例子:
样例输入
13 13
1 3
2 3
3 4
3 5
4 5
5 6
5 11
11 12
12 13
13 7
7 10
6 7
8 6
1 10
样例输出
3
对于这组样例数据,节点3、5、6和7是割点。节点6是一个割点,但不是路径(1到10)的关键点。因此,答案是3而不是4。
AC代码
#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>
#define AUTHOR "HEX9CF"
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
const int INF = 0x3f3f3f3f;
const ll MOD = 1e9 + 7;
struct Node {
int to;
int next;
} edge[N];
int head[N];
int cnt = 0;
int n, m;
int qu, qv;
// tarjan
int num = 0;
int dfn[N], low[N];
bitset<N> cut;
// bfs
queue<int> q1;
bitset<N> vis;
void init() {
cut.reset();
memset(head, -1, sizeof(head));
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
}
void add(int u, int v) {
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
// 求割点
void tarjan(int u, int fa) {
// 初始化
dfn[u] = low[u] = ++num;
int son = 0;
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa) {
continue;
}
if (!dfn[v]) {
// 访问子节点
tarjan(v, u);
// 回溯时更新
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) {
son++;
if (u != fa || son > 1) {
// u不是根节点或有两个以上子节点满足条件
cut[u] = 1;
}
}
} else {
// 回溯时更新
low[u] = min(low[u], dfn[v]);
}
}
}
bool bfs(int rm) {
// 初始化
vis.reset();
while (q1.size()) {
q1.pop();
}
q1.push(qu);
while (q1.size()) {
int f = q1.front();
// cout << f << endl;
q1.pop();
vis[f] = 1;
for (int i = head[f]; ~i; i = edge[i].next) {
int to = edge[i].to;
if (to == qv) {
// 能到达
return 1;
}
if (vis[to] || to == rm) {
continue;
}
// cout << to << " " << cut[to] << endl;
q1.push(to);
}
}
return 0;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
init();
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
cin >> qu >> qv;
if (qu == qv) {
// 若起点就是终点,危险系数就是0。
cout << 0 << "\n";
return 0;
}
// 求割点
tarjan(qu, qu);
if (!bfs(0)) {
// 起点和终点不连通,无法到达
cout << -1 << "\n";
return 0;
}
ll ans = 0;
// 找出路径上的关键点
for (int i = 1; i <= n; i++) {
if (cut[i] && !bfs(i)) {
// 移除该割点后无法到达终点的才是路径上的关键点
ans++;
}
}
cout << ans << "\n";
return 0;
}