#pragma GCC optimize("fast-math") // begin "cp-lib/boilerplate.hpp" #include #define _choose(_1, _2, _3, chosen, ...) chosen #define _rep(i, l, r) for (int i = l; i < r; ++i) #define _rep0(i, r) _rep(i, 0, r) #define _repr(i, r, l) for (int i = r; i >= l; --i) #define _repr0(i, r) _repr(i, r, 0) #define rep(...) _choose(__VA_ARGS__, _rep, _rep0, suppress_warning)(__VA_ARGS__) #define repr(...) _choose(__VA_ARGS__, _repr, _repr0, suppress_warning)(__VA_ARGS__) #define all(a) ::begin(a),::end(a) #define trav(a, b) for (auto&& a : b) using namespace std; namespace cp_lib {} using ll = long long; using ld = long double; using u64 = uint64_t; using u32 = uint32_t; [[maybe_unused]] static constexpr int INF = int(1e9 + 5); [[maybe_unused]] static constexpr ll INFL = ll(INF) * INF; [[maybe_unused]] static mt19937 rng(u32(chrono::duration_cast(chrono::high_resolution_clock::now().time_since_epoch()).count())); template int sz(const C& c) { return int(::size(c)); } // end "cp-lib/boilerplate.hpp" // begin "cp-lib/graph/_detail.hpp" namespace cp_lib { struct Identity { template constexpr T&& operator()(T&& t) const noexcept { return forward(t); } }; template struct Graph { const A& adj; int n; M map; Graph(const A& adj_, int n_, M&& map_) : adj{adj_}, n{n_}, map{forward(map_)} {} Graph(const A& adj_, M&& map_) : adj{adj_}, n{sz(adj)}, map{forward(map_)} {} Graph(const A& adj_, int n_) : adj{adj_}, n{n_}, map{Identity{}} {} explicit Graph(const A& adj_) : adj{adj_}, n{sz(adj)}, map{Identity{}} {} }; template Graph(const A&, int) -> Graph; template Graph(const A&) -> Graph; } // end "cp-lib/graph/_detail.hpp" // begin "cp-lib/graph/lca.hpp" // begin "../ds/rmq.hpp" // begin "../bit.hpp" namespace cp_lib { 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 floor_log2(n) + !is_pow2(n); } } // end "../bit.hpp" namespace cp_lib { namespace _rmq_detail { 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>; } // end "../ds/rmq.hpp" namespace cp_lib { struct LowestCommonAncestor { vector time, euler; Rmq rmq; template LowestCommonAncestor(G&& graph, int root) { auto [adj, n, to] = Graph(forward(graph)); if (!n) return; time.resize(n); vector d; int t = 0; auto dfs = [&, &adj=adj, &to=to](int v, int p, auto&& self) -> void { time[v] = t++; trav(e, adj[v]) if (to(e) != p) euler.push_back(v), d.push_back(time[v]), self(to(e), v, self); }; dfs(root, -1, 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; return minmax(lca(a, v), lca(b, v)) == minmax(v, lca(a, b)); } }; } // end "cp-lib/graph/lca.hpp" using namespace cp_lib; template void find_bridges(G&& graph, F&& f) { auto [adj, n, to] = Graph(forward(graph)); vector idx(n, -1), stack; auto dfs = [&, &adj=adj, &to=to](int v, int p, auto&& self) -> int { int up = idx[v] = sz(stack); stack.push_back(v); for (auto it = begin(adj[v]), it_end = end(adj[v]); it != it_end; ++it) { int v2 = to(*it); if (v2 == p) { p = -1; continue; } int up2 = (idx[v2] == -1 ? self(v2, v, self) : idx[v2]); if (up2 > idx[v]) f(v, it, begin(stack) + idx[v2], end(stack)), stack.resize(idx[v2]); up = min(up, up2); } return up; }; rep(i, n) { if (idx[i] != -1) continue; dfs(i, -1, dfs); f(-1, end(adj[0]), all(stack)); stack.clear(); } } struct BlockCutTree { vector node; vector is_cut; vector> tree; template explicit BlockCutTree(G&& graph) { auto [adj, n, to] = Graph(forward(graph)); vector idx = node = vector(n, -1), stack; is_cut = vector(n, false); auto make_component = [&](int rem) { int vt = sz(tree); tree.emplace_back(); while (sz(stack) > rem) { int v = stack.back(); stack.pop_back(); if (!is_cut[v]) node[v] = vt; else if (!sz(tree[node[v]]) || tree[node[v]].back() != vt) tree[vt].push_back(node[v]), tree[node[v]].push_back(vt); } }; auto dfs = [&, &adj=adj, &to=to](int v, int p, int8_t root, auto&& self) -> int { int up = idx[v] = sz(stack); trav(e, adj[v]) { int v2 = to(e); if (v2 == p) continue; if (idx[v2] == -1 || idx[v2] < idx[v]) stack.push_back(v), stack.push_back(v2); if (idx[v2] != -1) { up = min(up, idx[v2]); continue; } int up2 = self(v2, v, 0, self); up = min(up, up2); if (up2 >= idx[v]) { if (root == 2) { --root; continue; } if (!is_cut[v]) is_cut[v] = true, node[v] = sz(tree), tree.emplace_back(); make_component(idx[v2] - 2); if (root == 1) --root, make_component(0); } } return up; }; rep(i, n) { if (idx[i] != -1) continue; dfs(i, -1, 2, dfs); if (sz(stack)) make_component(0); } } }; int main() { cin.tie(0)->sync_with_stdio(false); int n, m; cin >> n >> m; vector adj(n, vector()); rep(_, m) { int u, v; cin >> u >> v; --u, --v; adj[u].push_back(v); adj[v].push_back(u); } BlockCutTree bct(adj); LowestCommonAncestor lca(bct.tree, 0); vector depth(sz(bct.tree), -1); auto dfs = [&](int v, int p, auto&& self) -> void { trav(v2, bct.tree[v]) if (v2 != p) depth[v2] = depth[v] + 1, self(v2, v, self); }; rep(i, sz(bct.tree)) if (depth[i] == -1) depth[i] = 0, dfs(i, -1, dfs); int q; cin >> q; while (q--) { int u, v; cin >> u >> v; --u, --v; int ut = bct.node[u], vt = bct.node[v]; int l = lca.lca(ut, vt); int d = depth[ut] + depth[vt] - 2 * depth[l]; d -= bct.is_cut[u] + bct.is_cut[v]; cout << max(0, d / 2) << '\n'; } }