题解:P10953 逃不掉的路

Firsry AC/WA/RE/TLE

边双+树剖求

前情提要

本篇题解不讲解边双以及树剖的具体过程,如有需要,请移步两篇大佬的题解:

树链剖分求 LCA 边双连通分量

题目分析

a 城到 b 城不管怎么走,总有一些逃不掉的必经之路。

很显然的是一个边双连通分量中的边一定不是“必经之路”,因为删除这条路也可以走出去,所以可以通过 求解边双连通分量进行缩点操作,考虑对于剩下的无向无环图进行处理。而无向无环图这个名字我们是不熟悉的,我们熟悉的是——树。

所以问题转化为树上两个点之间求解必经之路。这个在树上就一目了然:求 ,两个结点之间经过 的路径上的路就是必经之路,其个数也就是边权为 的最短路了,用深度之差的方法,不必多言。

所以代码的结构就出来了:

  1. 原图的建立;
  2. 求边双连通分量;
  3. 缩点建立无向无环图(树);
  4. 进行树剖;
  5. 的方法解决树上两点距离问题。

上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include<bits/stdc++.h>

inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
x = x * 10 + ch - '0', ch = getchar();
return x * f;
}

using namespace std;

const int MAXN = 100005;
const int MAXM = 200005;

int n, m, q;

int edgeCount = 1;
int head[MAXN], toNode[MAXM << 1], nextEdge[MAXM << 1], fromNode[MAXM << 1];

int countDfn;
int dfn[MAXN];

int ECC;
stack<int> dfsStack;
int low[MAXN], belong[MAXN];

int newEdgeCount;
int newHead[MAXN], newToNode[MAXM << 1], newNextEdge[MAXM << 1];
int siz[MAXN], dep[MAXN], fat[MAXN];
int son[MAXN], top[MAXN];

void add(int from, int to) {
edgeCount++;
toNode[edgeCount] = to;
fromNode[edgeCount] = from;
nextEdge[edgeCount] = head[from];
head[from] = edgeCount;
return;
}
void in() {
int a, b;
n = read(), m = read();
for (int i = 1; i <= m; ++i) {
a = read(), b = read();
add(a, b), add(b, a);
}
q = read();
return;
}
void tarjan(int from, int fromEdge) {
countDfn++;
dfn[from] = low[from] = countDfn;
dfsStack.push(from);
for (int i = head[from]; i; i = nextEdge[i]) {
int to = toNode[i];
if (!dfn[to]) {
tarjan(to, i);
low[from] = min(low[from], low[to]);
} else if (i != (fromEdge ^ 1))
low[from] = min(low[from], dfn[to]);
}
if (dfn[from] == low[from]) {
ECC++;
belong[from] = ECC;
while (dfsStack.top() != from) {
belong[dfsStack.top()] = ECC;
dfsStack.pop();
}
dfsStack.pop();
}
return;
}

void newAdd(int from, int to) {
newEdgeCount++;
newToNode[newEdgeCount] = to;
newNextEdge[newEdgeCount] = newHead[from];
newHead[from] = newEdgeCount;
return;
}
void newIn() {
for (int i = 2; i <= (m << 1); i += 2) {
int from = fromNode[i], to = toNode[i];
if (belong[from] ^ belong[to]) {
newAdd(belong[from], belong[to]);
newAdd(belong[to], belong[from]);
}
}
return;
}
void dfs1(int from, int father) {
siz[from]++;
fat[from] = father;
dep[from] = dep[father] + 1;
for (int i = newHead[from]; i; i = newNextEdge[i]) {
int to = newToNode[i];
if (to != father) {
dfs1(to, from);
siz[from] += siz[to];
if (siz[son[from]] < siz[to])
son[from] = to;
}
}
return;
}
void dfs2(int from, int curTop) {
top[from] = curTop;
if (!son[from])
return;
dfs2(son[from], curTop);
for (int i = newHead[from]; i; i = newNextEdge[i]) {
int to = newToNode[i];
if (to != fat[from] && to != son[from])
dfs2(to, to);
}
return;
}
inline int lca(int a, int b) {
while (top[a] != top[b]) {
if (dep[top[a]] < dep[top[b]])
swap(a, b);
a = fat[top[a]];
}
return dep[a] < dep[b] ? a : b;
}

int main() {
int a, b, anc;
in();
tarjan(1, 0);
newIn();
dfs1(1, 0);
dfs2(1, 1);
while (q--) {
a = belong[read()], b = belong[read()];
anc = lca(a, b);
cout << dep[a] + dep[b] - (dep[anc] << 1) << '\n';
}
return 0;
}

闲话

这道题是本蒟蒻第一次拿下 Luogu.com.cn 最优解(至少截至 04-05 21:36:15)并在 0:48:49 的用时内不看题解怒切 提高+/省选-。这是 AC 记录

  • Title: 题解:P10953 逃不掉的路
  • Author: Firsry
  • Created at : 2025-08-10 08:53:30
  • Updated at : 2025-08-10 09:24:32
  • Link: https://firsryfan.github.io/2025/08/10/题解:P10953-逃不掉的路/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
题解:P10953 逃不掉的路