Official

L - 消防署と緊急出動 / Fire Station and Emergency Dispatch Editorial by physics0523


重心分解 を用いることでこの問題に正解できます。
以下の行為を考えます。

  • 木からある頂点 \(r\) を選ぶ。
  • 頂点 \(v\) に対してクエリが飛んできたとき、以下の仮定を置く。
    • \(v\) から \(r\) を経由して、最寄りの消防署 \(w\) に到達する。

このとき、 \(r\) を取り除いた森について、 \(v\) と \(w\) が同じ連結成分になければ 答えが正しく求まります。 … (★)

この考えを拡張します。
木には、以下の条件を満たす頂点 \(g\) が必ず存在します。

  • \(g\) を取り除いた森について、全ての連結成分のサイズが元の木のサイズの半分以下となる。

この \(g\) を再帰的に取っていくことで、元の木のサイズを \(N\) としたとき最大深さ \(O(\log N)\) の新たな木を作ることができます。この木は各頂点の祖先に (再帰的に取った際の) 重心たちが並びます。これが重心分解です。
さらに、木が潰れるまで再帰を繰り返すことで、 (★) で書かれた例外が無くなるまで木を取り尽くすことができます。

結局、どう解けば良いのでしょうか?

  • 重心分解を行い、新たな木 \(T\) を作る。
  • タイプ \(1\) について、自身および \(T\) 上での祖先に対して頂点集合の変更を処理する。
  • タイプ \(2\) について、自身および \(T\) 上での祖先に対して質問を行う。

各タイプのクエリの更新が時間計算量 \(O(f(N))\) かかるとすると、クエリ \(1\) 回あたりの時間計算量は \(O(f(N) \log N)\) となります。

特定の \(2\) 頂点間の距離は LCA (最小共通祖先) を適切に改造することで求めることができます。

これを全て実装することで、時間計算量 \(O(N \log N+Q \log^2 N)\) でこの問題に正解できます。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;

using Graph=vector<vector<int>>;

// based on:
// https://kopricky.github.io/code/GraphDecomposition/centroid_decomposition.html
struct CentDec{
  int V;
  vector<bool> used;
  vector<int> sz, par;
  CentDec(const Graph &G){ init(G); }

  void calcSize(int u,int p,const Graph &G){
    sz[u] = 1;
    for(int v : G[u]){
      if(!used[v] && v != p){
        calcSize(v,u,G);
        sz[u] += sz[v];
      }
    }
  }

  void cdBuild(int u,int p,const Graph &G){
    calcSize(u,-1,G);
    int tot = sz[u];
    bool ok = false;
    int pp = -1;
    while(!ok){
      ok = true;
      for(int v : G[u]){
        if(!used[v] && v != pp && 2*sz[v] > tot){
          pp = u, u = v, ok = false;
          break;
        }
      }
    }
    par[u] = p;
    used[u] = true;
    for(int v : G[u]){
      if(!used[v]){
        cdBuild(v,u,G);
      }
    }
  }

  void init(const Graph &G){
    V=G.size();
    used.resize(V);
    sz.resize(V);
    par.resize(V);
    for(int i=0;i<V;i++){
      used[i]=false;
      sz[i]=0;
      par[i]=-1;
    }
    cdBuild(0,-1,G);
  }
};

// based on:
// https://algo-logic.info/lca/
struct LCA {
  vector<vector<int>> parent;
  vector<int> dist;
  LCA(const Graph &G, int root = 0) { init(G, root); }

  void init(const Graph &G, int root = 0) {
    int V = G.size();
    int K = 1;
    while ((1 << K) < V) K++;
    parent.assign(K, vector<int>(V, -1));
    dist.assign(V, -1);
    dfs(G, root, -1, 0);
    for (int k = 0; k + 1 < K; k++) {
      for (int v = 0; v < V; v++) {
        if (parent[k][v] < 0) {
          parent[k + 1][v] = -1;
        } else {
          parent[k + 1][v] = parent[k][parent[k][v]];
        }
      }
    }
  }

  void dfs(const Graph &G, int v, int p, int d) {
    parent[0][v] = p;
    dist[v] = d;
    for (auto e : G[v]) {
      if (e != p) dfs(G, e, v, d + 1);
    }
  }

  int query(int u, int v) {
    if (dist[u] < dist[v]) swap(u, v);
    int K = parent.size();
    for (int k = 0; k < K; k++) {
      if ((dist[u] - dist[v]) >> k & 1) {
        u = parent[k][u];
      }
    }
    if (u == v) return u;
    for (int k = K - 1; k >= 0; k--) {
      if (parent[k][u] != parent[k][v]) {
        u = parent[k][u];
        v = parent[k][v];
      }
    }
    return parent[0][u];
  }
};

using ll=long long;
using weiGraph=vector<vector<pair<int,ll>>>;

vector<ll> d;
vector<int> exi;
vector<multiset<ll>> ms;

ll dist(int u,int v,LCA &lca){
  int l=lca.query(u,v);
  return d[u]+d[v]-2*d[l];
}

void toggle(int v,LCA &lca,CentDec &cd){
  int beg=v;
  exi[beg]^=1;
  while(v>=0){
    if(exi[beg]==1){
      // add
      ms[v].insert(dist(beg,v,lca));
    }
    else{
      // erase
      ms[v].erase(ms[v].find(dist(beg,v,lca)));
    }
    v=cd.par[v];
  }
}

ll ask(int v,LCA &lca,CentDec &cd){
  int beg=v;
  ll res=8e18;
  while(v>=0){
    if(!ms[v].empty()){
      res=min(res,dist(beg,v,lca)+(*ms[v].begin()));
    }
    v=cd.par[v];
  }
  return res;
}

int main(){
  int N,Q;
  cin >> N >> Q;
  ms.resize(N);
  Graph g(N);
  weiGraph wg(N);
  for(int i=1;i<N;i++){
    int A,B,C;
    cin >> A >> B >> C;
    A--; B--;
    g[A].push_back(B);
    g[B].push_back(A);
    wg[A].push_back({B,C});
    wg[B].push_back({A,C});
  }

  exi.resize(N);
  for(auto &nx : exi){nx=0;}

  {
    d.resize(N);
    for(auto &nx : d){nx=-1;}
    d[0]=0;
    queue<int> q;
    q.push(0);
    while(!q.empty()){
      int od=q.front(); q.pop();
      for(auto [v,w] : wg[od]){
        if(d[v]==-1){
          d[v]=d[od]+w;
          q.push(v);
        }
      }
    }
  }

  LCA lca(g,0);
  CentDec cd(g);

  toggle(0,lca,cd);
  while(Q--){
    int typ;
    cin >> typ;
    if(typ==1){
      int X;
      cin >> X;
      X--;
      toggle(X,lca,cd);
    }
    else{
      int V;
      ll T;
      cin >> V >> T;
      V--;
      if(ask(V,lca,cd)<=T){cout << "YES\n";}
      else{cout << "NO\n";}
    }
  }
  return 0;
}

posted:
last update: