QOJ #5439. Meet in the Middle 题解

Description

Byteland 有 \(n\) 座城市,从 \(1\)\(n\) 编号,且有 \(n-1\) 条双向公路和 \(n-1\) 条双向铁路连接它们。

对于任意一对城市,都可以仅通过公路互相到达;同样,仅通过铁路也能互相到达。

Alice 和 Bob 计划在 Byteland 进行 \(q\) 次长途旅行。在每次旅行中,Alice 从第 \(a\) 座城市出发,只通过公路访问一些其他城市;Bob 从第 \(b\) 座城市出发,只通过铁路访问一些其他城市。

两人最终会在同一座目的城市会合,并且在整个旅途中都不会重复访问任何城市。

你需要求出,在所有目的城市的选择中,他们路线总长度的最大值。

\(n\leq 10^5,q\leq 5\times 10^5\)

Solution

首先对于一棵树,每次求距离某个点最远的点,有两种做法,第一种是离线下来用数据结构维护距离;第二种方法是由于最远的点一定是直径端点,所以可以求出全局直径后 \(O(1)\) 做。

这里有两棵树,用某一种方法都不太好做,考虑将这两种方法结合起来。

先把询问离线下来,对第一棵树做第一种方法,即按照 dfs 的顺序枚举询问在第一棵树的点 \(x\),在移动的过程中维护 \(w_i\) 表示 \(i\)\(x\) 的距离。

现在问题等价于求 \(w_i+dis_2(i,y)\) 的最大值,这个对第二棵树的每个点 \(j\),建立一个虚点 \(j'\),与 \(j\) 连边权值为 \(w_j\) 就能将问题转化为直径问题。

但是移动 \(x\) 的过程中 \(w_i\) 会变化,考虑用线段树维护子树内节点的直径。

注意到线段树修改的过程是找到若干个线段树区间 \([l,r]\) 再区间打标记,由于整体打标记不会影响直径,所以找到这些区间后向上合并直径就行了。

时间复杂度:\(O(n\log n+q)\)

Code

#include <bits/stdc++.h>

#define int int64_t

using pii = std::pair<int, int>;

const int kMaxN = 1e5 + 5, kMaxQ = 5e5 + 5;

int n, q, pos;
int res[kMaxQ];
std::vector<std::pair<int, int>> qq[kMaxN];

struct Tree {
  int dfn_cnt, dfn[kMaxN], idx[kMaxN], sz[kMaxN], dis[kMaxN], st[kMaxN][18];
  std::vector<std::pair<int, int>> G[kMaxN];
  int get(int x, int y) { return dfn[x] < dfn[y] ? x : y; }
  void add(int u, int v, int w) { G[u].emplace_back(v, w), G[v].emplace_back(u, w); }
  void dfs(int u, int fa) {
    st[dfn[u] = ++dfn_cnt][0] = fa, idx[dfn_cnt] = u, sz[u] = 1;
    for (auto [v, w] : G[u]) {
      if (v == fa) continue;
      dis[v] = dis[u] + w;
      dfs(v, u);
      sz[u] += sz[v];
    }
  }
  int LCA(int x, int y) {
    if (x == y) return x;
    if (dfn[x] > dfn[y]) std::swap(x, y);
    int k = std::__lg(dfn[y] - dfn[x]);
    return get(st[dfn[x] + 1][k], st[dfn[y] - (1 << k) + 1][k]);
  }
  int getdis(int x, int y) { return dis[x] + dis[y] - 2 * dis[LCA(x, y)]; }
  void prework() {
    dfn_cnt = 0, dfs(1, 0);
    for (int i = 1; i <= std::__lg(dfn_cnt); ++i)
      for (int j = 1; j <= dfn_cnt - (1 << i) + 1; ++j)
        st[j][i] = get(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
  }
} t1, t2;

int calc(int x, int y) { return t1.getdis(pos, x) + t1.getdis(pos, y) + t2.getdis(x, y); }

pii merge(pii a, pii b) {
  if (!a.first) return b;
  if (!b.first) return a;
  std::vector<int> vec = {a.first, a.second, b.first, b.second};
  pii ret = {0, 0};
  int now = -1e9;
  for (int i = 0; i < 4; ++i) {
    for (int j = i + 1; j < 4; ++j) {
      int val = calc(vec[i], vec[j]);
      if (val > now) ret = {vec[i], vec[j]}, now = val;
    }
  }
  return ret;
}

struct SGT {
  pii t[kMaxN * 4];
  void pushup(int x) { t[x] = merge(t[x << 1], t[x << 1 | 1]); }
  void build(int x, int l, int r) {
    if (l == r) return void(t[x] = {t1.idx[l], t1.idx[l]});
    int mid = (l + r) >> 1;
    build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
    pushup(x);
  }
  void update(int x, int l, int r, int ql, int qr) {
    if (l > qr || r < ql) return;
    else if (l >= ql && r <= qr) return;
    int mid = (l + r) >> 1;
    update(x << 1, l, mid, ql, qr), update(x << 1 | 1, mid + 1, r, ql, qr);
    pushup(x);
  }
} sgt;

void dfs(int u, int fa) {
  for (auto [b, id] : qq[u]) {
    auto [x, y] = sgt.t[1];
    res[id] = std::max({res[id], t1.getdis(x, u) + t2.getdis(x, b), t1.getdis(y, u) + t2.getdis(y, b)});
  }
  for (auto [v, w] : t1.G[u]) {
    if (v == fa) continue;
    pos = v, sgt.update(1, 1, n, t1.dfn[v], t1.dfn[v] + t1.sz[v] - 1);
    dfs(v, u);
    pos = u, sgt.update(1, 1, n, t1.dfn[v], t1.dfn[v] + t1.sz[v] - 1);
  }
}

void dickdreamer() {
  std::cin >> n >> q;
  for (int i = 1; i < n; ++i) {
    int u, v, w;
    std::cin >> u >> v >> w;
    t1.add(u, v, w);
  }
  for (int i = 1; i < n; ++i) {
    int u, v, w;
    std::cin >> u >> v >> w;
    t2.add(u, v, w);
  }
  for (int i = 1; i <= q; ++i) {
    int a, b;
    std::cin >> a >> b;
    qq[a].emplace_back(b, i);
  }
  t1.prework(), t2.prework();
  pos = 1, sgt.build(1, 1, n);
  dfs(1, 0);
  for (int i = 1; i <= q; ++i) std::cout << res[i] << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2025-08-14 20:43  下蛋爷  阅读(20)  评论(0)    收藏  举报