Solution - Codeforces 622E Ants in Leaves

rizynvu發表於2024-10-11

首先因為 \(1\) 點是可以一次性到多個點的,因此不需要考慮 \(1\) 點的情況,而是轉而分析 \(1\) 的每個子樹的情況,最後取 \(\max\)

那麼對於每個子樹,就有每個節點每個時刻至多存在 \(1\) 個點的性質了。
考慮如何去求解。

首先一個貪心的想法是肯定是每個螞蟻越早到一個點越好。
於是一個想法是考慮在 dfs 的時候合併。
即維護子樹 \(u\) 內葉子到 \(u\) 的時間 \(S\),子樹 \(v\) 內葉子到 \(v\) 的時間 \(T\)
那麼如果存在 \(x\in S\) 滿足 \(x\in T\),那麼就欽定這個螞蟻先不往上爬,而是等到 \(x + 1\) 時刻繼續爬,即 \(x\leftarrow x + 1\) 直到 \(x\not \in T\)

但是問題在於合併的次數太多了,這個合併的方法也不太好用資料結構維護(其實可以啟發式合併 + 線段樹二分做到 \(\mathcal{O}(n\log^2 n)\))。

考慮到一個 \(x\in S\),這個 \(x\) 會和許多其他集合 \(T\) 合併,但是合併操作都是若 \(x\in T\),則 \(x\leftarrow x + 1\)
那麼這說明實際上各個集合是不區分的,那麼就可以直接把所有的葉子放在根處合併。

即直接維護可重集合 \(S = \{\operatorname{dep}_u | u \operatorname{is\ leaf}\}\),那麼若 \(S\) 中存在 \(c(c\ge 1)\)\(x\),就直接欽定一個 \(x\) 到根,而剩下的 \(c - 1\)\(x\) 就只能變為 \(x + 1\) 繼續等待。
\(x\) 從小到大操作直到 \(S\) 為空,變為空的那個時刻就代表著最後一個葉子的螞蟻跳到了根,這個時刻就是這個子樹的答案。

容易發現這是與上述的遞迴合併過程等價的,因為這個讓 \(c - 1\)\(x\) 變為 \(x + 1\) 就代表著在合併過程中發現了其他的 \(x\) 選擇了讓步。

然後考慮最佳化 \(S\) 的變化的過程。
其實能夠發現真正只關注每個 \(x\) 的個數 \(\operatorname{cnt}_x\)
那麼就可以直接遞推,當 \(\operatorname{cnt}_x\ge 1\) 時有 \(\operatorname{cnt}_{x + 1}\leftarrow \operatorname{cnt}_{x + 1} + \operatorname{cnt}_x\)

然後考慮一下這個過程中 \(x\) 最大會是多少。
一個很寬的上界是 \(\operatorname{size}(T) + \max \operatorname{dep}(T)\le 2\operatorname{size}(T)\)
又因為 \(1\) 的所有子樹 \(\operatorname{size}\) 之和 \(= n - 1\)
所以可以知道實際上 \(\sum \max x\le 2n - 2\)

於是時間複雜度就為 \(\mathcal{O}(n)\)

#include<bits/stdc++.h>
const int maxn = 5e5 + 10;
std::vector<int> to[maxn];
int dep[maxn], cnt[maxn * 2], tot;
int dfs(int u, int fa) {
   dep[u] = dep[fa] + 1;
   if (to[u].size() == 1)
      cnt[dep[u]]++, tot++;
   int siz = 1;
   for (int v : to[u])
      if (v != fa)
         siz += dfs(v, u);
   return siz;
}
int main() {
   int n; scanf("%d", &n);
   for (int i = 1, x, y; i < n; i++)
      scanf("%d%d", &x, &y), to[x].push_back(y), to[y].push_back(x);
   int mx = 0;
   for (int u : to[1]) {
      int siz = dfs(u, 1);
      for (int i = 1; ; i++) {
         if (cnt[i])
            tot--, cnt[i + 1] += cnt[i] - 1;
         if (! tot) {
            mx = std::max(mx, i); break;
         }
      }
      memset(cnt + 1, 0, sizeof(int) * siz * 2);
   }
   return printf("%d\n", mx), 0;
}

相關文章