AOJ 0627 Train Fare (鉄道運賃)

解法

まず首都からの最短経路を求めるほかどうしようもないので,BFSで最初に求めておく.
このとき,任意の頂点について,すべての最短経路でのその頂点の親と子を求めておく.
経路復元するときと同じ感じでやればいい.

クエリが投げられたら,その辺が最短経路で使われているか確認する.
これは辺 (u, v) に対して u と v が親子関係か調べれば良い.
使われていれば,仮に親を u,子を v とすると,u の子どもの集合から v を取り除き,v の親の集合から u を取り除く.
親の集合のサイズが 0 になったときが,最短経路が崩れてしまう条件なので,この時 v の親の集合が 0 になったら,v を親として,さっきと同じことを伝播させてやっていく.
取り除いた頂点は別に管理しておけば良く,クエリの最後にはこの取り除いた頂点のサイズを答える.

ソースコード

#include <bits/stdc++.h>
using namespace std;
 
using pii = pair<int, int>;

constexpr int INF = 1e9;

void erase(int p, int ch, vector<set<int>>& par, vector<set<int>>& child, set<int>& upset) {
    queue<pii> que;
    que.push(make_pair(p, ch));
    while(!que.empty()) {
        int pp = que.front().first;
        int c = que.front().second;
        que.pop();
        par[c].erase(pp);
        child[pp].erase(c);
        if(par[c].empty() && upset.count(c) == 0) {
            upset.insert(c);
            for(auto& cc : child[c]) {
                que.push(make_pair(c, cc));
            }
        }
    }
}

int main() {
    int N, M, Q;
    cin >> N >> M >> Q;
    vector<vector<int>> g(N);
    vector<pii> es(M);
    for(int i = 0; i < M; ++i) {
        int u, v;
        cin >> u >> v;
        u--; v--;
        es[i].first = u;
        es[i].second = v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    vector<int> d(N, INF);
    vector<set<int>> par(N);
    vector<set<int>> child(N);
    queue<pii> que;
    que.push(make_pair(0, 0));
    d[0] = 0;
    while(!que.empty()) {
        int v = que.front().first;
        int cost = que.front().second;
        que.pop();
        for(auto to : g[v]) {
            if(d[to] >= cost + 1) {
                child[v].insert(to);
                par[to].insert(v);
            }
            if(d[to] > cost + 1) {
                d[to] = cost + 1;
                que.push(make_pair(to, d[to]));
            }
        }
    }

    set<int> upset;
    while(Q--) {
        int r;
        cin >> r;
        r--;
        int p = es[r].first, c = es[r].second;
        if(d[p] + 1 != d[c]) {
            swap(p, c);
        }
        if(d[p] + 1 == d[c]) {
            erase(p, c, par, child, upset);
        }
        cout << upset.size() << endl;
    }
}

感想

もっと簡単に書ける気がするんだけど,実装方針あんまり考えずに実装してしまった.