题目地址:
https://www.acwing.com/problem/content/399/
现代社会,路是必不可少的。共有 n n n个城镇, m m m条道路,任意两个城镇都有路相连,而且往往不止一条。但有些路年久失修,走着很不爽。按理说条条大路通罗马,大不了绕行其他路呗——可小撸却发现:从 a a a城到 b b b城不管怎么走,总有一些逃不掉的必经之路。他想请你计算一下, a a a到 b b b的所有路径中,有几条路是逃不掉的?
输入格式:
第一行是
n
n
n和
m
m
m,用空格隔开。接下来
m
m
m行,每行两个整数
x
x
x和
y
y
y,用空格隔开,表示
x
x
x城和
y
y
y城之间有一条长为
1
1
1的双向路。第
m
+
2
m+2
m+2行是
q
q
q。接下来
q
q
q行,每行两个整数
a
a
a和
b
b
b,用空格隔开,表示一次询问。
输出格式:
对于每次询问,输出一个正整数,表示
a
a
a城到
b
b
b城必须经过几条路。每个输出占一行。
数据范围:
n
≤
1
0
5
,
m
≤
2
×
1
0
5
,
q
≤
1
0
5
n≤10^5,m≤2\times 10^5,q≤10^5
n≤105,m≤2×105,q≤105
对于全部的数据,
1
≤
x
,
y
,
a
,
b
≤
n
1≤x,y,a,b≤n
1≤x,y,a,b≤n;对于任意的道路,两端的城市编号之差不超过
1
0
4
10^4
104;任意两个城镇都有路径相连;同一条道路不会出现两次;道路的起终点不会相同;查询的两个城市不会相同。
必经之路其实就是图的桥。先求双连通分量,求完之后缩点,则原图会变成一棵树。应答询问 a a a和 b b b之间的路径有多少条边必须走,其实就是问 a a a所在的双连通分量到 b b b所在的双连通分量有多少条边。在树中求两个点的距离,可以先任意指定一个顶点为树根,然后按最近公共祖先的方式,预处理出 f [ v ] [ k ] f[v][k] f[v][k]这个数组, f [ v ] [ k ] f[v][k] f[v][k]表示从 v v v出发向树根走 2 k 2^k 2k条边走到哪个点,另外还需要处理出每个点的深度 d [ v ] d[v] d[v](即 v v v到树根需要走多少条边),那么在询问树中两个点 a a a和 b b b的距离的时候,可以先求最近公共祖先 p p p,则两个点距离为 d [ a ] + d [ b ] − 2 d [ p ] d[a]+d[b]-2d[p] d[a]+d[b]−2d[p]。代码如下:
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 1e5 + 10, M = 4e5 + 10;
int n, m, s;
int h[N], h2[N], e[M << 1], ne[M << 1], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int id[N], dcc_cnt;
int dep[N], f[N][20];
int q[N];
void add(int a, int b, int h[]) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 求双连通分量
void tarjan(int u, int from) {
dfn[u] = low[u] = ++timestamp;
stk[top++] = u;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
} else if (v != from) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
dcc_cnt++;
int y;
do {
y = stk[--top];
id[y] = dcc_cnt;
} while (y != u);
}
}
// 预处理出深度dep数组和f数组
void bfs(int u) {
memset(dep, -1, sizeof dep);
int hh = 0, tt = 0;
q[tt++] = u;
dep[u] = 0;
while (hh < tt) {
int t = q[hh++];
for (int i = h2[t]; ~i; i = ne[i]) {
int v = e[i];
if (~dep[v]) continue;
dep[v] = dep[t] + 1;
f[v][0] = t;
for (int k = 1; k <= log2(dep[v]); k++)
f[v][k] = f[f[v][k - 1]][k - 1];
q[tt++] = v;
}
}
}
// 应答最近公共祖先
int lca(int a, int b) {
if (dep[a] < dep[b]) swap(a, b);
for (int k = 0, diff = dep[a] - dep[b]; 1 << k <= diff; k++)
if (diff >> k & 1) a = f[a][k];
if (a == b) return a;
for (int k = log2(dep[a]); k >= 0; k--)
if (f[a][k] != f[b][k])
a = f[a][k], b = f[b][k];
return f[a][0];
}
int main() {
memset(h, -1, sizeof h);
// h2存缩点之后的图,即树
memset(h2, -1, sizeof h2);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b, h), add(b, a, h);
}
tarjan(1, -1);
for (int i = 1; i <= n; i++)
for (int j = h[i]; ~j; j = ne[j]) {
int v = e[j];
int a = id[i], b = id[v];
if (a != b) add(a, b, h2), add(b, a, h2);
}
bfs(1);
scanf("%d", &s);
while (s--) {
int a, b;
scanf("%d%d", &a, &b);
a = id[a], b = id[b];
int p = lca(a, b);
printf("%d\n", dep[a] + dep[b] - 2 * dep[p]);
}
}
预处理时间复杂度 O ( n + m ) O(n+m) O(n+m),每次询问时间 O ( log n ) O(\log n) O(logn),空间 O ( n log n ) O(n\log n) O(nlogn)。