Official

Ex - Difference of Distance Editorial by en_translator


Let \(G_n\) be a graph obtained by retaining the edges with weight at most \(n\) in the original graph.

Let \(D_j\) be the value of \(d(S_j,T_j)\) in the original graph. Also, let \(\mathcal P\) be a set of paths connecting vertices \(S_j\) and \(T_j\) on the original graph such that “the maximum weight of an edge in the path” equals \(D_j\).

  • If \(W_{A_j} > D_j\): for all \(P \in \mathcal P\), edge \(A_j\) is not in \(P\), so increasing the weight of edge \(A_j\) does not change the maximum weight of an edge in the path \(P\); it remains \(D_j\). Thus, \(d(S_j,T_j)\) does not change as well.

  • If \(W_{A_j} < D_j\): incrementing the weight of edge \(A_j\) results in \(W_{A_j} \leq D_j\), so for all \(P \in \mathcal P\), the maximum weight of an edge in the path \(P\) remains \(D_j\). Thus, \(d(S_j,T_j)\) is invariant.

  • If \(W_{A_j} = D_j\): if there is a path \(P \in \mathcal P\) that does not contain edge \(A_j\), then the maximum weight of an edge in the path \(P\) remains \(D_j\), so \(d(S_j,T_j)\) is not affected. Conversely, if \(P\) contains edge \(A_j\) for all \(P \in \mathcal P\), then \(d(S_j,T_j)\) increases by one.

We can easily compare \(W_{A_j}\) and \(D_j\) by checking whether vertices \(S_j\) and \(T_j\) are connected in \(G_{W_{A_j}}\) and \(G_{W_{A_j}-1}\), thus using a Disjoint Set Union.

The issue left is to determine for each query\(W_{A_j} = D_j\) if there is a path \(P \in \mathcal P\) without edge \(A_j\). Here, there is no path \(P \in \mathcal P\) without edge edge \(A_j\) if and only if:

  • on a graph obtained by truncating all edges with weights less than \(W_{A_j}\) in \(G_{W_{A_j}}\), edge \(A_j\) is a bridge; moreover, when we remove edge \(A_j\) to obtain two connected components, one of them contains vertex \(S_j\), and the other contains vertex \(T_j\).

Here, a “bridge” of a graph is a term refers to an edge such that removing it results in increasing the number of connected components. Given a graph with \(V\) vertices and \(E\) edges, an algorithm called LowLink enables us to enumerate the bridges in the graph in a total of \(O(V+E)\) time.

Thus, it is sufficient to determine the following two on “the graph obtained by truncating all edges with weights less than \(W_{A_j}\) in \(G_{W_{A_j}}\)”:

  • Is edge \(A_j\) a bridge?
  • If so, after removing edge \(A_j\) to obtain two connected component, does one of them contain vertex \(S_j\) and the other contain \(T_j\)?

The former can be checked fast with LowLink, and the latter with DFS-order. Thus, the problem has been solved. The complexity of the following implementation is \(O(N+M\alpha (N) + Q\log Q +(M+Q) \log M)\).

Bonus: although the complexity is worsen, instead of using LowLink we can use the similar algorithm as ABC295-G.

Sample code (C++):

#include<bits/stdc++.h>
#include "atcoder/dsu"

using namespace std;
using namespace atcoder;

class lowlink {
    int n;
    vector<vector<pair<int, int>>> G;
    vector<int> in, out, low;
    unordered_map<int, int> bridge;
    
    void dfs(int u, int p, int &k) {
        in[u] = low[u] = k++;
        for (auto [v, id]: G[u]) {
            if (in[v] < 0) {
                dfs(v, id, k);
                low[u] = min(low[u], low[v]);
                if (low[v] > in[u]) bridge[id] = v;
            } else if (id != p) {
                low[u] = min(low[u], in[v]);
            }
        }
        out[u] = k;
    }
    
    void init() {
        n = G.size();
        in.assign(n, -1);
        out.assign(n, -1);
        low.assign(n, -1);
        int k = 0;
        for (int i = 0; i < n; i++) {
            if (in[i] < 0) dfs(i, -1, k);
        }
    }

public:
    lowlink(const vector<vector<pair<int, int>>> &G) : G(G) { init(); }
    
    bool query(int a, int s, int t) {
        assert(0 <= s and s < n);
        assert(0 <= t and t < n);
        assert(s != t);
        if (!bridge.count(a)) return false;
        int l = in[bridge[a]], r = out[bridge[a]];
        s = in[s], t = in[t];
        return (l <= s and s < r) xor (l <= t and t < r);
    }
};

int main() {
    int n, m;
    cin >> n >> m;
    vector<int> u(m), v(m), w(m);
    vector<vector<int>> weight(m);
    for (int i = 0; i < m; i++) {
        cin >> u[i] >> v[i] >> w[i];
        --u[i], --v[i], --w[i];
        weight[w[i]].push_back(i);
    }
    int q;
    cin >> q;
    vector<int> a(q), s(q), t(q);
    for (int i = 0; i < q; i++) {
        cin >> a[i] >> s[i] >> t[i];
        --a[i], --s[i], --t[i];
    }
    vector<int> ans(q, -1);
    vector<int> ord(q);
    iota(ord.begin(), ord.end(), 0);
    sort(ord.begin(), ord.end(), [&](int i, int j) { return w[a[i]] < w[a[j]]; });
    
    // Process the queries where original d(s[j], t[j]) does not coincides with w[a[j]]
    dsu uf(n);
    int l = 0;
    for (int i = 0; i < m; i++) {
        int r = l;
        while (r < q and w[a[ord[r]]] == i) ++r;
        for (int j = l; j < r; j++) {
            if (uf.same(s[ord[j]], t[ord[j]])) ans[ord[j]] = 0;
        }
        for (int j: weight[i]) {
            uf.merge(u[j], v[j]);
        }
        for (int j = l; j < r; j++) {
            if (!uf.same(s[ord[j]], t[ord[j]])) ans[ord[j]] = 0;
        }
        l = r;
    }
    
    uf = dsu(n);
    int it = 0;
    for (int i = 0; i < m; i++) {
        vector<int> ls; // The set of representative elements of the currenct connected components that are connected to a weight-i edge
        for (int j: weight[i]) {
            ls.push_back(uf.leader(u[j]));
            ls.push_back(uf.leader(v[j]));
        }
        sort(ls.begin(), ls.end());
        ls.erase(unique(ls.begin(), ls.end()), ls.end());
        int sz = ls.size();
        vector<vector<pair<int, int>>> G(sz); // edge = (to, id)
        for (int j: weight[i]) {
            int nu = lower_bound(ls.begin(), ls.end(), uf.leader(u[j])) - ls.begin();
            int nv = lower_bound(ls.begin(), ls.end(), uf.leader(v[j])) - ls.begin();
            if(nu != nv) {
                G[nu].emplace_back(nv, j);
                G[nv].emplace_back(nu, j);
            }
        }
        lowlink lk(G);
        while (it < q and w[a[ord[it]]] == i) {
            int now = ord[it++];
            if (ans[now] != -1) continue;
            int ns = lower_bound(ls.begin(), ls.end(), uf.leader(s[now])) - ls.begin();
            int nt = lower_bound(ls.begin(), ls.end(), uf.leader(t[now])) - ls.begin();
            assert(ns != nt);
            assert(ns < sz and nt < sz);
            assert(ls[ns] == uf.leader(s[now]) and ls[nt] == uf.leader(t[now]));
            // Does ns-nt path always contain a[now] on G?
            ans[now] = lk.query(a[now], ns, nt);
        }
        for (int j: weight[i]) {
            uf.merge(u[j], v[j]);
        }
    }
    
    for (int i = 0; i < q; i++) cout << ans[i] << '\n';
}

posted:
last update: