CF526G Spiders Evil Plan 題解

下蛋爷發表於2024-07-28

Description

給定一棵 \(n\) 個節點的無根樹,每條邊有邊權。

\(q\) 次詢問,每次詢問給出 \(x,y\),你需要選擇 \(y\) 條樹上的路徑,使這些路徑形成一個包含 \(x\) 的連通塊,且連通塊中包含的邊權和最大。

\(n, q \le 10^5\),強制線上。

Solution

考慮只有一組詢問怎麼快速求答案。

容易發現樹上路徑的兩端點一定是葉子,並且如果給定了這些葉子那麼一定存在一組方案使得這些路徑覆蓋了這些葉子構成的虛樹。

對於詢問 \(x,y\),考慮把 \(x\) 提到根,需要找到一些葉子使得這些葉子的虛樹包含 \(x\) 並且虛樹權值和最大。

如果只要求權值和最大就只需要找到權值最大的 \(2y\) 條長鏈即可,這就是貪心選,但是此時選擇的虛樹可能不包含 \(x\) 而導致這種以 \(x\) 為根貪心取長鏈算出來的答案會算上一些虛樹沒有的邊。

由於這是暴力就不考慮怎麼調整了。

注意到選擇葉子的最優解一定滿足至少選了樹的直徑的兩端點之一,所以考慮讓直徑的兩端點作為根跑上面那個貪心做法,這樣就不用換根了。

具體的,假設直徑的一端點為 \(p\),詢問為 \((x,y)\),就以 \(p\) 為根選擇最長的 \(2y-1\) 條長鏈加入答案。可能出現選的方案虛樹不包含 \(x\) 的情況,這時需要調整。容易發現只有兩種調整方案:

  1. 找到 \(x\) 祖先裡第一個被選的點然後把這個點到葉子的路徑替換為這個點走到 \(x\) 那邊的路徑。
  2. \(x\) 所在的長鏈替換原來第 \(2y-1\) 長的長鏈。

至於為什麼是這兩種就考慮 \(x\) 的長鏈替換哪個原長鏈即可。

時間複雜度:\(O\left((n+q)\log n\right)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e5 + 5;

int n, q, s, t, cntl;
int dis[kMaxN];
std::vector<std::pair<int, int>> G[kMaxN];

void dfs(int u, int fa) {
  if (!fa) dis[u] = 0;
  for (auto [v, w] : G[u]) {
    if (v == fa) continue;
    dis[v] = dis[u] + w;
    dfs(v, u);
  }
}

struct Tree {
  int rt, cnt, tot;
  int lg[kMaxN], len[kMaxN], dis[kMaxN], mxd[kMaxN], lson[kMaxN], p[kMaxN][18];
  int rnk[kMaxN], id[kMaxN], sum[kMaxN], minrnk[kMaxN], top[kMaxN];
  
  void dfs1(int u, int fa) {
    mxd[u] = u, p[u][0] = fa;
    for (int i = 1; i <= lg[n]; ++i)
      p[u][i] = p[p[u][i - 1]][i - 1];
    for (auto [v, w] : G[u]) {
      if (v == fa) continue;
      dis[v] = dis[u] + w;
      dfs1(v, u);
      if (len[v] + w > len[u])
        len[u] = len[v] + w, mxd[u] = mxd[v], lson[u] = v;
    }
  }

  void dfs2(int u, int fa) {
    minrnk[u] = rnk[u];
    for (auto [v, w] : G[u]) {
      if (v == fa) continue;
      dfs2(v, u);
      minrnk[u] = std::min(minrnk[u], minrnk[v]);
    }
  }

  void init(int s) {
    rt = s, lg[0] = -1;
    for (int i = 1; i <= n; ++i) lg[i] = lg[i >> 1] + 1;
    dfs1(rt, 0);
    for (int i = 1; i <= n; ++i) {
      if (i == rt || i != lson[p[i][0]]) {
        for (int j = i; j; j = lson[j]) top[j] = i;
      }
    }
    std::vector<std::pair<int, int>> vec;
    for (int i = 1; i <= n; ++i) {
      rnk[i] = 1e9;
      if (i == rt || i != lson[p[i][0]]) {
        vec.emplace_back(dis[mxd[i]] - dis[p[i][0]], mxd[i]);
      }
    }
    std::sort(vec.begin(), vec.end(), std::greater<std::pair<int, int>>());
    tot = vec.size();
    for (int i = 0; i < (int)vec.size(); ++i) {
      rnk[vec[i].second] = i + 1;
      id[i + 1] = vec[i].second, sum[i + 1] = sum[i] + vec[i].first;
    }
    dfs2(rt, 0);
  }

  int ask1(int x, int y) {
    int now = x;
    for (int i = lg[n]; ~i; --i)
      if (p[now][i] && minrnk[p[now][i]] > y)
        now = p[now][i];
    if (minrnk[now] > y) now = p[now][0];
    return sum[y] - len[now] + dis[mxd[x]] - dis[now];
  }

  int ask2(int x, int y) {
    int now = x;
    for (int i = lg[n]; ~i; --i)
      if (p[now][i] && minrnk[p[now][i]] >= y)
        now = p[now][i];
    if (minrnk[now] >= y) now = p[now][0];
    return sum[y - 1] + dis[mxd[x]] - dis[now];
  }

  int solve(int x, int y) {
    y = 2 * y - 1;
    if (y >= tot) return sum[tot];
    else if (minrnk[x] <= y) return sum[y];
    return std::max(ask1(x, y), ask2(x, y));
  }
} tr[2];

void dickdreamer() {
  std::cin >> n >> q;
  int sumw = 0;
  for (int i = 1; i < n; ++i) {
    int u, v, w;
    std::cin >> u >> v >> w;
    G[u].emplace_back(v, w), G[v].emplace_back(u, w);
    sumw += w;
  }
  for (int i = 1; i <= n; ++i) cntl += (G[i].size() == 1);
  dfs(1, 0);
  s = std::max_element(dis + 1, dis + 1 + n) - dis;
  dfs(s, 0);
  t = std::max_element(dis + 1, dis + 1 + n) - dis;
  tr[0].init(s), tr[1].init(t);
  for (int i = 1, lstans = 0; i <= q; ++i) {
    int x, y;
    std::cin >> x >> y;
    x = (x + lstans - 1) % n + 1, y = (y + lstans - 1) % n + 1;
    if (2 * y >= cntl)std::cout << (lstans = sumw) << '\n';
    else std::cout << (lstans = std::max(tr[0].solve(x, y), tr[1].solve(x, y))) << '\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;
}

相關文章