AT_agc009_d [AGC009D] Uninity

lalaouye發表於2024-11-24

這題看完題解後遲遲不下手寫程式碼,因為這道題實在是太厲害了!

考慮對於一棵樹手玩這個過程,發現如果一個點要作為中間的一個節點,它肯定會掛上周圍的所有點所在的樹,當然它之後掛的點除外。這事實上是一個點分樹的過程,那麼該問題就是求最大深度最小的點分樹,發現並不好做。

好在它剛剛告訴我答案是 \(\log n\) 級別的,這樣似乎會好做一些。現在徹底清除剛剛自己思考的廢料,只保留剛剛挖掘出的答案的級別,繼續思考性質,發現一棵樹能作為一棵 Uninity \(k\) 的樹的充要條件是給每個點賦一個權,對於權相同的兩個點的路徑上必須得存在一個點權大於自己的點,切整棵樹的點權最大值為 \(k\),而一個點在問題中掛了若干棵 Uninity \(k\) 的數,則這個點點權為 \(k+1\)

這個性質貌似可以在考慮兩棵樹的時候想到,因為要想把兩棵樹連起來,顯然要有一個點能讓這兩棵樹掛在一起,而這兩棵樹掛的點顯然大於這兩棵樹裡的點的點權。

所以現在問題轉化了,我們只需要求使得最大值最小的合法的點權分配方式即可。考慮貪心的子樹轉移,設 \(S_u\) 表示子樹內有哪些點權在到 \(u\) 的路徑中不存在比該點權大的點,那麼當前點權 \(a_u\) 現在不在任意一個 \(S_{son_u}\) 裡面出現,而如果有至少兩棵子樹的集合中存在相同的點權,那麼當前點權 \(a_u\) 得保證大於這些點權,因為需要滿足這兩個路徑中存在比自己大的點權。

直接二進位制轉移即可。

程式碼:

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i (l); i <= r; ++ i)
#define rrp(i, l, r) for (int i (r); i >= l; -- i)
#define pii pair <int, int>
#define eb emplace_back
#define inf 1000000000
using namespace std;
constexpr int N = 1e5 + 5, M = 310, P = 1e9 + 7;
typedef unsigned long long ull;
typedef long long ll;
inline ll rd () {
  ll x = 0, f = 1;
  char ch = getchar ();
  while (! isdigit (ch)) {
    if (ch == '-') f = -1;
    ch = getchar ();
  }
  while (isdigit (ch)) {
    x = (x << 1) + (x << 3) + ch - 48;
    ch = getchar ();
  }
  return x * f;
}
void add (int &x, int y) {
  (x += y) >= P ? (x -= P) : (x); 
}
int qpow (int x, int y) {
  int ret = 1;
  for (; y; y >>= 1, x = x * x % P) if (y & 1) ret = ret * x % P;
  return ret;
}
int n;
vector <int> e[N];
int ans, f[N];
void dfs (int u, int fa) {
  int s = 0, p = 0;
  for (auto v : e[u]) {
    if (v == fa) continue;
    dfs (v, u);
    rep (i, 0, 20) {
      if ((s >> i & 1) && (f[v] >> i & 1)) p = max (p, i + 1); 
    } s |= f[v];
  }
  while (s >> p & 1) ++ p;
  ans = max (ans, p);
  f[u] = s;
  f[u] |= 1 << p;
  f[u] = (f[u] >> p) << p;
}
int main () {
  // freopen ("1.in", "r", stdin);
  // freopen ("1.out", "w", stdout);
  n = rd ();
  rep (i, 2, n) {
    int u = rd (), v = rd ();
    e[u].eb (v), e[v].eb (u);
  }
  dfs (1, 0);
  cout << ans;
}