【洛谷 P8604】[蓝桥杯 2013 国 C] 危险系数 题解(Tarjan算法+无向图+求割点+链式前向星+广度优先搜索)

[蓝桥杯 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(2n1000) m ( 0 ≤ m ≤ 2000 ) m(0 \le m \le 2000) m(0m2000),分别代表站点数,通道数。

接下来 m m m 行,每行两个整数 u , v ( 1 ≤ u , v ≤ n , u ≠ v ) u,v(1 \le u,v \le n,u\neq v) u,v(1u,vn,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,表示起点和终点不连通。

注意

  1. 若起点就是终点,危险系数就是0,直接输出0即可。
  2. 需要判断起点和终点是否连通。若不连通,直接输出-1即可。
  3. 割点不一定是关键点,有可能存在这样的情况:路径上某个点是割点,但是移除该点后,仍能通过其他路径能到达终点。需要对每个割点进行判断,若移除该割点后无法到达终点的,才是路径上的关键点。举个例子:

样例输入

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。

1
2
3
4
5
6
7
8
10
11
12
13

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;
}

  • 37
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值