// https://yukicoder.me/problems/5539 #pragma GCC optimize("fast-math") #include #ifdef LOCAL # include #else # define dbg(...) do {} while (0) #endif #define cp_lib_4th(_1, _2, _3, x, ...) x #define cp_lib_rep(i, l, r) for (int i = (l); (i) < (r); ++(i)) #define cp_lib_rep0(i, r) cp_lib_rep(i, 0, r) #define rep(...) cp_lib_4th(__VA_ARGS__, cp_lib_rep, cp_lib_rep0, _)(__VA_ARGS__) #define cp_lib_repr(i, r, l, ...) for (int i = (r); (i) >= (l); --(i)) #define repr(...) cp_lib_repr(__VA_ARGS__, 0) #define all(a) ::begin(a),::end(a) #define trav(a, b) for (auto&& a : (b)) using namespace std; using ll = long long; using ld = long double; [[maybe_unused]] static constexpr int INF = int(1e9 + 5); [[maybe_unused]] static constexpr ll INFL = ll(INF) * INF; template int sz(const C& c) { return int(::size(c)); } #ifdef CP_LIB_DEBUG #define cp_lib_assert(expr) \ do { if (!(expr)) { \ ::cerr << "assertion failed: " << #expr << " (" << __FILE__ << ':' << __LINE__ << ")\n"; \ ::abort(); \ } } while (0) #else #define cp_lib_assert(expr) #endif #if __cplusplus < 202002L struct identity { template constexpr T&& operator()(T&& t) const noexcept { return forward(t); }; }; #endif #if __cpp_lib_remove_cvref < 201711L template using remove_cvref_t = remove_cv_t>; #endif namespace cp_lib_type_meta { template constexpr bool is_tuple_like = false; template constexpr bool is_tuple_like>> = true; } namespace cp_lib_modint { struct ModIntTag {}; } #include namespace cp_lib_io { constexpr int BUF_SIZE = 1 << 20; constexpr array, 10'000> DIGITS = []{ array, 10'000> digits{}; for (int i = 3, d = 1; i >= 0; --i, d *= 10) rep(j, 10'000) digits[j][i] = char('0' + j / d % 10); return digits; }(); array ibuf, obuf; char *iptr = data(ibuf), *iend = iptr, *optr = data(obuf); template constexpr bool is_std_array = false; template constexpr bool is_std_array> = true; void flush() { for (auto* p = begin(obuf); p != optr; p += write(STDOUT_FILENO, p, optr - p)); optr = begin(obuf); } int _flush_atexit = []{ atexit(flush); return 0; }(); void refill() { memmove(begin(ibuf), iptr, iend - iptr); iend -= iptr - begin(ibuf); iptr = begin(ibuf); iend += read(STDIN_FILENO, iend, end(ibuf) - 1 - iend); *iend = '\0'; } template > void print(T&& val) { if (end(obuf) - optr < 64) flush(); if constexpr (is_same_v) *optr++ = val; else if constexpr (is_same_v || is_same_v::reference>) return print(int(val)); else if constexpr (is_integral_v && is_signed_v) { if (val < 0) *optr++ = '-'; using U = make_unsigned_t; return print(U(val < 0 ? -U(val) : U(val))); } else if constexpr (is_integral_v && is_unsigned_v) { T2 val2 = val; array tmp; char* tptr = end(tmp); while (val2 >= 10'000) tptr -= 4, memcpy(tptr, &DIGITS[val2 % 10'000][0], 4), val2 /= T2(10'000); int d = (val2 >= 100 ? (val2 >= 1000 ? 4 : 3) : (val2 >= 10 ? 2 : 1)); memcpy(optr, &DIGITS[val2][4 - d], d); memcpy(optr + d, tptr, end(tmp) - tptr); optr += d + int(end(tmp) - tptr); } else if constexpr (is_floating_point_v) optr += sprintf(optr, "%.30Lf", (long double)val); else if constexpr (is_convertible_v) { string_view sv(val); if (sz(sv) + 1 <= end(obuf) - optr) memcpy(optr, data(sv), sz(sv)), optr += sz(sv); else { flush(); for (auto *p = data(sv), *pe = p + sz(sv); p != pe; p += write(STDOUT_FILENO, p, pe - p)); } } else if constexpr (is_base_of_v) return print(decltype(T2::mod())(val)); else if constexpr (cp_lib_type_meta::is_tuple_like && !is_std_array) return apply([](auto&&... items) { (print(items), ...); }, forward(val)); else { trav(item, val) print(item); return; } *optr++ = ' '; } template void read(T& val) { auto skip_ws = [] { do { for (; iptr != iend && *iptr <= ' '; ++iptr); if (iend - iptr < 64) refill(); } while (*iptr <= ' '); }; auto read_other = [&](auto other) { read(other); return other; }; if constexpr (is_same_v) skip_ws(), val = *iptr++; else if constexpr (is_same_v || is_same_v::reference>) { val = bool(read_other(uint8_t())); } else if constexpr (is_base_of_v) { val = T(read_other(ll())); } else if constexpr (is_integral_v) { skip_ws(); if (is_signed_v && *iptr == '-') ++iptr, val = T(-read_other(make_unsigned_t())); else for (val = 0; iptr != iend && *iptr > ' '; val = T(10 * val + (*iptr++ & 15))); } else if constexpr (is_floating_point_v) skip_ws(), val = T(strtold(iptr, &iptr)); else if constexpr (is_same_v) { skip_ws(); val.clear(); do { auto* after = find_if(iptr, iend, [](char c) { return c <= ' '; }); val.append(iptr, after); if ((iptr = after) != iend) break; refill(); } while (iptr != iend); } else if constexpr (cp_lib_type_meta::is_tuple_like && !is_std_array) apply([](auto&... items) { (read(items), ...); }, val); else trav(item, val) read(item); } } using cp_lib_io::flush; template void print(Args&&... args) { (cp_lib_io::print(forward(args)), ...); } template void println(Args&&... args) { if (sizeof...(Args)) (cp_lib_io::print(forward(args)), ...), *(cp_lib_io::optr - 1) = '\n'; else print('\n'), --cp_lib_io::optr; } template void read(Args&... args) { (cp_lib_io::read(args), ...); } template >>> auto graph(A&& adj, M&& m = M()) { return make_tuple(ref(adj), sz(adj), m); } template >>> auto graph(G g) { return g; } namespace cp_lib_graph { template using AdjListIter = decltype(begin(get<0>(graph(declval()))[0])); } namespace cp_lib_graph { template >, int> = 0> auto maybe_edge_idx(T v) { return pair(int(v), 0); } template >, int> = 0> auto maybe_edge_idx(T p) { auto [v, e] = p; return pair(int(v), int(e)); } } template void find_biconnected_components(G&& g, OnComp&& on_comp) { auto [adj, n, map] = graph(forward(g)); vector idx(n, INF); vector>> stack; auto dfs = [&, &adj = adj, &map = map](int v, pair pe, auto&& self) -> int { int up = idx[v] = sz(stack), cnt = 0; for (auto it = begin(adj[v]); it != end(adj[v]); ++it) { auto [v2, e] = cp_lib_graph::maybe_edge_idx(map(*it)); if (pair(v2, e) == pe) continue; up = min(up, idx[v2]); if (idx[v2] == INF || idx[v2] < idx[v]) stack.emplace_back(v, it); if (idx[v2] != INF) continue; int up2 = self(v2, pair(v, e), self); up = min(up, up2); if (up2 >= idx[v]) { auto cut_v = (pe.first == -1 && !cnt++ ? nullopt : optional(v)); on_comp(cut_v, begin(stack) + idx[v2] - 1, end(stack)); stack.resize(idx[v2] - 1); } } return up; }; rep(i, n) if (idx[i] == INF) dfs(i, pair(-1, 0), dfs); } template auto block_vertex_tree(G&& g) { auto [adj, n, to] = graph(forward(g)); vector tree(n, vector()); find_biconnected_components(adj, [&, &to = to](auto, auto it, auto it_end) { int vc = sz(tree); tree.emplace_back(); for (; it != it_end; ++it) for (int v : {it->first, to(*it->second)}) if (!sz(tree[v]) || tree[v].back() != vc) tree[v].push_back(vc), tree[vc].push_back(v); }); return tree; } template constexpr inline bool is_pow2(T n) { return n && !(n & (n - 1)); } template constexpr inline int floor_log2(T n) { return n ? 63 - __builtin_clzll(n) : 0; } template constexpr inline int ceil_log2(T n) { return n ? floor_log2(n) + !is_pow2(n) : 0; } namespace cp_lib_rmq { template T minmax(T a, T b) { return (a < b) ^ Max ? a : b; } } template struct SparseTable { vector> d = {}; SparseTable() = default; explicit SparseTable(const vector& v) { int n = sz(v), k = floor_log2(n); d.assign(k + 1, v); rep(i, k) rep(j, n) d[i + 1][j] = Combine(d[i][j], d[i][min(j + (1 << i), n - 1)]); } // Aggregate for [l, r) T query(int l, int r) const { int k = floor_log2(r - l); return Combine(d[k][l], d[k][r - (1 << k)]); } }; template using Rmq = SparseTable>; template using Rmaxq = SparseTable>; struct LowestCommonAncestor { vector time, euler; Rmq rmq; template >>> LowestCommonAncestor(G&& g, int root = 0) { auto [adj, n, to] = graph(forward(g)); if (!n) return; time = vector(n, -1); vector d; auto dfs = [&, t = 1, &adj = adj, &to = to](int v, int p, int tp, auto&& self) mutable -> void { euler.push_back(p); d.push_back(tp); time[v] = t++; trav(e, adj[v]) if (to(e) != p) self(to(e), v, time[v], self); }; dfs(root, -1, 0, dfs); rep(i, n) if (time[i] == -1) dfs(i, -1, 0, dfs); rmq = Rmq(d); } int lca(int u, int v) const { if (u == v) return u; auto [l, r] = minmax(time[u], time[v]); return euler[rmq.query(l, r)]; } bool on_path(int v, pair path) const { auto [a, b] = path; auto av = lca(a, v), bv = lca(b, v), ab = lca(a, b); return av != -1 && bv != -1 && ab != -1 && minmax(av, bv) == minmax(v, ab); } bool is_ancestor(int u, int v) const { return lca(u, v) == u; } }; int main() { int n, m; read(n, m); vector adj(n, vector()); rep(_, m) { int u, v; read(u, v); --u, --v; adj[u].push_back(v); adj[v].push_back(u); } auto bvt = block_vertex_tree(adj); int nt = sz(bvt); LowestCommonAncestor lca(bvt); vector depth(nt, 0); auto dfs = [&](int v, int p, auto&& self) -> void { trav(v2, bvt[v]) if (v2 != p) depth[v2] = depth[v] + 1, self(v2, v, self); }; dfs(0, -1, dfs); int q; read(q); while (q--) { int u, v; read(u, v); --u, --v; int d = depth[u] + depth[v] - 2 * depth[lca.lca(u, v)]; println(max(0, d / 2 - 1)); } }