题目
传送门 to AtCoder
思路
对于我来说,超级大难题,完全没思路;对 H I D \sf HID HID 来说,垃圾小水题,随便做出来。
题目初步转化一下:构建一棵类点分树,使得高度最小。如果自顶向下建树,则有结论 a n s ∈ { k , k + 1 } ans\in\{k,\;k{\rm+}1\} ans∈{k,k+1},然而完全没用。也可考虑找重心的调整法——显然不行。
我又考虑 自底向上 建树。原树的叶子,在点分树上也一定是叶子。于是,与原树的叶子相邻的,是点分树的倒数第二层。但是再往上呢?我感觉,如果可以是 1 1 1 那就是 1 1 1 。或许可证;那么再往上呢?由于它并不形式化,我终究失败了。
本题要做的,就是 将上述思考写成定理。所以又是结论题是吧。只从上面这种形象化的描述的角度来讲,很难阐明一些东西。
定理:设 f : V ↦ N f:V\mapsto\N f:V↦N,即给每个点标号,满足对于任意 f ( x ) = f ( y ) ( x ≠ y ) f(x)=f(y)\;(x\ne y) f(x)=f(y)(x=y),存在 z ∈ path ( x , y ) z\in\text{path}(x,y) z∈path(x,y) 使得 f ( z ) > f ( x ) f(z)>f(x) f(z)>f(x),记 v ( f ) = max f ( x ) v(f)=\max f(x) v(f)=maxf(x),则答案为 min v ( f ) \min v(f) minv(f) 。
显然任意合法解都对应这样一个 f f f 。接下来只需要证明,这样的 f f f 又可以反过来构造一组解,且这个解的答案不超过 v ( f ) v(f) v(f) 。利用归纳法,一个点时显然成立;多个点时,找到 max f ( x ) \max f(x) maxf(x),显然这是唯一的。将其作为点分树的根,对每个连通块施加归纳,其答案不超过 v ( f ′ ) ⩽ v ( f ) − 1 v(f')\leqslant v(f)-1 v(f′)⩽v(f)−1,于是这个方案的答案不超过 v ( f ′ ) + 1 ⩽ v ( f ) v(f')+1\leqslant v(f) v(f′)+1⩽v(f),证毕。
然后,我们可以 贪心地递归处理。其来源,就是自底向上建树的想法,但这里我们可以给出更简洁、更形式化的证明。设 y < f ( x ) y<f(x) y<f(x),若令 f ( x ) = y f(x)=y f(x)=y 后 x x x 的子树仍然是合法点分树(只考虑子树内的点时,满足定理的 f f f 映射),设 z z z 为 x x x 的树上父亲,则再令 f ( z ) = max [ f ( z ) , f ( x ) ] f(z)=\max[f(z),f(x)] f(z)=max[f(z),f(x)] 即可得到新的合法点分树(这里的 f ( x ) f(x) f(x) 是原映射值)。
验证该方案的合法性是容易的:跨过 x x x 的路径,如果跨过 z z z 就仍然合法,因为 f ( z ) f(z) f(z) 不小于原来的 f ( x ) f(x) f(x);不跨过 z z z 也仍然合法,这是前提条件。不跨过 x x x 的路径,必定仍合法。
而 v ( f ) v(f) v(f) 不会变。所以 f ( x ) f(x) f(x) 的取值是唯一的,就是恰好使得子树成为合法点分树的最小值。那么,在原树上 d f s \tt dfs dfs,存储每个子树内哪些 f ( x ) f(x) f(x) 是需要避开的(冇更大的 f f f 将其 “遮挡” 住)即可。考虑到 f ( x ) ⩽ log n f(x)\leqslant\log n f(x)⩽logn,可以状压,时间复杂度 O ( n ) \mathcal O(n) O(n) 。
代码
#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG long for LONELINESS)
#include <cctype> // ZXY yydSISTER!!!
#include <vector>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void getMin(int &x, const int &y){
if(y < x) x = y;
}
const int MAXN = 100005;
struct Edge{
int to, nxt;
Edge() = default;
Edge(int _t, int _n): to(_t), nxt(_n){}
};
Edge e[MAXN<<1]; int head[MAXN], cntEdge;
void addEdge(int a, int b){
e[cntEdge] = Edge(b,head[a]), head[a] = cntEdge ++;
}
int ans;
int dfs(int x, int pre){
int s = 0, bad = 0;
for(int i=head[x]; ~i; i=e[i].nxt){
if((i^1) == pre) continue;
const int &&ch = dfs(e[i].to,i);
bad |= s&ch, s |= ch;
}
int chose = bad ? 32-__builtin_clz(bad) : 0;
chose += __builtin_ffs((s>>chose)+1)-1;
ans = max(ans,chose); return ((s>>chose)|1)<<chose;
}
int main(){
int n = readint();
memset(head+1,-1,n<<2);
for(int i=1,a,b; i!=n; ++i){
a = readint(), b = readint();
addEdge(a,b), addEdge(b,a);
}
dfs(1,-1); printf("%d\n",ans);
return 0;
}