【ACWing】397. 逃不掉的路

这篇博客介绍了如何利用图的双连通分量和最近公共祖先(LCA)来解决图中找寻必经之路的问题。通过tarjan算法求双连通分量,然后进行树的预处理,最后使用LCA算法回答询问。这种方法在预处理阶段的时间复杂度为O(n+m),每次查询的时间复杂度为O(logn),空间复杂度为O(nlogn)。
摘要由CSDN通过智能技术生成

题目地址:

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 n105,m2×105,q105
对于全部的数据, 1 ≤ x , y , a , b ≤ n 1≤x,y,a,b≤n 1x,y,a,bn;对于任意的道路,两端的城市编号之差不超过 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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值