神奇贪心题。
差点题目没有看懂,这道题的逻辑是,一个树的最小 \(\text{Uninity}\) 可以这么计算:选择一个点 \(u\),把这个树分成若干个树递归下去,最后该树的 \(\text{Uninity}\) 为 \(\max \text{Uninity}_v+1\),在所有 \(u\) 中选择最终结果最小的那个。这样时间复杂度大概是 \(O(n^n)\)。
该划分是一种点分树,如果按照重心点分树来划分,答案的级别为 \(O(\log n)\),所以答案的规模小于 \(O(\log n)\)。
考虑转化题意,考虑一个最小的 \(\text{Uninity}\) 编号方案,此时每个点都对应了一个编号。我们反过来对点进行标号,那么该编号方案合法的充要条件是:
- 对于任意两个编号为 \(k\) 的点 \((u,v)\),满足他们路径上存在一个点为 \(k+1\)。
必要性很好证,充分性可以考虑从大到小遍历编号,显然最大的一个编号只会有一个,分成若干个树后每个树的最大编号肯定也只有一个,如此一直下去,到了最后一个点你可以认为他的 \(\text{Uninity}\) 任意。
考虑使用贪心,我们从下往上贪心,我们设集合 \(s_{u}\) ,若 \(t\in s_u\),则表示 \(u\) 子树内是否存在一个编号为 \(t\) 的点到达 \(u\) 这条路径上有比他大的点。
我们这个点一定尽量填小的,设 \(lab_u\) 表示 \(u\) 上面的编号,考虑他需要满足什么条件:
- 对于 \(t\in c_{v_1},t\in c_{v_2}\),\(lab_u \gt t\)。
- 对于 \(t \in c_v\),\(lab_u \neq t\)。
用位运算进行计算即可。
__builtin_clz
为 \(\text{unsigned int}\) 类型下为 \(1\) 的最高位以上的 \(0\) 的个数。
__builtin_ctz
最低位 \(1\) 前面 \(0\) 的个数。
#include<bits/stdc++.h>
using namespace std;
#define N 100005
int n,lab[N],s[N];
basic_string<int> G[N];
void dfs(int u,int fa){
int cap=0;
for(int v:G[u])if(v!=fa){
dfs(v,u);
cap|=(s[u]&s[v]);
s[u]|=s[v];
}
int t=(cap?32-__builtin_clz(cap):0);
lab[u]=__builtin_ctz((s[u]|((1<<t)-1))+1);
s[u]=(((s[u]>>lab[u])|1)<<lab[u]);
}
int main(){
scanf("%d",&n);
for(int i=1,u,v;i<n;i++)scanf("%d%d",&u,&v),G[u]+=v,G[v]+=u;
dfs(1,0);
int ans=0;
for(int i=1;i<=n;i++)ans=max(ans,lab[i]);
printf("%d",ans);
}