CF1307F Cow and Vacation

先判掉 \(dis(s,t)\le k\) 的情况,现在路径一定会经过关键点。

先只考虑关键点之间可达性,显然如果关键点 \(u\) 能到关键点 \(v\),那么 \(v\) 也能到 \(u\)。于是我们只要求出连通块。

距离为 \(r\) 相当于 \(u\) 能到的是一个半径为 \(k\) 的区域,这个不是很好连边。但是有一个折半的想法,我们考虑每个点半径为 \(\frac{k}{2}\) 的区域,那么两个点可以互相到达相当于它们对应的区域相切,那么我们可以在切点处合并。这个在实现上就是从所有关键点同时 BFS \(\frac{k}{2}\) 轮,如果有两个关键点同时到达了一个点 \(x\) 就把它们合并。一个细节是 \(k\) 为奇数时不好处理,这个可以把边拆点,\(k\) 就翻倍成偶数了。

现在我们已知了关键点之间的可达性,还需要从 \(s\)\(t\) 分别找第一步关键点,不妨考虑 \(s\) 的情况。这里有一个结论:取 \(p\)\(s\to t\) 链上与 \(s\) 距离为 \(\frac{k}{2}\) 的点 \(q\),那么第一步取离 \(q\) 最近的关键点 \(p\) 即可。最近的关键点可以在 BFS 时求出。

这个结论的证明:如果有解,则存在一个解,设其第一步到 \(p'\),链上与 \(p'\) 距离最近点为 \(q'\),有 \(dis(s,p')\ge dis(p',q')\)。然后讨论 \(q\) 是否在 \(s\to q'\) 上,可以发现 \(dis(p,p')\le k\) 总是成立。

于是我们只要找出第一步和最后一步,然后并查集判一下即可。时间复杂度 \(O((n+q)\log n)\)

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

const int inf = 1e9;
const int kN = 4e5 + 5;
int n, k, r, q;
int fa[kN], jp[kN], dep[kN];
vector<int> g[kN];

void DFS(int x, int fa) {
  ::fa[x] = fa;
  dep[x] = dep[fa] + 1;
  int dif1 = dep[fa] - dep[jp[fa]];
  int dif2 = dep[jp[fa]] - dep[jp[jp[fa]]];
  jp[x] = (dif1 == dif2) ? jp[jp[fa]] : fa;
  for(int to : g[x]) {
    if(to != fa) DFS(to, x);
  }
}
int KthAnc(int x, int k) {
  int gl = dep[x] - k;
  while(dep[x] > gl) x = (dep[jp[x]] >= gl) ? jp[x] : fa[x];
  return x;
}
int LCA(int x, int y) {
  if(dep[x] < dep[y]) swap(x, y);
  x = KthAnc(x, dep[x] - dep[y]);
  while(x != y) {
    if(jp[x] != jp[y]) x = jp[x], y = jp[y];
    else x = fa[x], y = fa[y];
  }
  return x;
}

pii nbr[kN];
bool vis[kN];

struct DSU {
  int fa[kN];
  DSU() { iota(fa, fa + kN, 0); }
  int Find(int x) {
    return (fa[x] == x) ? x : (fa[x] = Find(fa[x]));
  }
  void Merge(int x, int y) {
    fa[Find(x)] = Find(y);
  }
}dsu;

void BFS() {
  queue<int> q;
  for(int i = 1; i <= 2 * n; i++) nbr[i] = pii {inf, -1};
  for(int i = 1; i <= r; i++) {
    int x;
    cin >> x;
    vis[x] = 1, q.push(x);
    nbr[x] = pii {0, x};
  }
  for(int t = 1; t <= k / 2; t++) {
    int siz = q.size();
    while(siz--) {
      int x = q.front(); q.pop();
      for(int to : g[x]) {
        if(!vis[to]) {
          vis[to] = 1, q.push(to);
          nbr[to] = pii {t, nbr[x].second};
        }else if(nbr[to].first == t) {
          dsu.Merge(nbr[x].second, nbr[to].second);
        }
      }
    }
  }
}

bool Query(int s, int t) {
  int lca = LCA(s, t);
  int dis = dep[s] + dep[t] - 2 * dep[lca];
  if(dis <= k) return 1;
  auto Get = [&](int s, int t) -> int {
    int anc;
    if(dep[s] - dep[lca] >= k / 2) anc = KthAnc(s, k / 2);
    else anc = KthAnc(t, dis - k / 2);
    if(nbr[anc].first > k / 2) return -1;
    return nbr[anc].second;
  };
  int ids, idt;
  if((ids = Get(s, t)) == -1) return 0;
  if((idt = Get(t, s)) == -1) return 0;
  return dsu.Find(ids) == dsu.Find(idt);
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> k >> r, k *= 2;
  for(int i = 1; i < n; i++) {
    int u, v;
    cin >> u >> v;
    g[u].push_back(i + n);
    g[i + n].push_back(u);
    g[v].push_back(i + n);
    g[i + n].push_back(v);
  }
  DFS(1, 0), BFS();
  cin >> q;
  for(int i = 1; i <= q; i++) {
    int s, t;
    cin >> s >> t;
    cout << (Query(s, t) ? "YES" : "NO") << "\n";
  }
  return 0;
}
posted @ 2025-07-05 15:12  CJzdc  阅读(22)  评论(0)    收藏  举报