Official
L - 消防署と緊急出動 / Fire Station and Emergency Dispatch Editorial
by
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:
