【AtCoder Grand Contest 009 D】Uninity
点分治思想,贪心。
题目大意:
定义:
- 一个结点的树是 Uninity $0$ 的树。
- 将 $x$($x\geq 0$)棵 Uninity $k$ 的树连到一个结点上,形成的新树是 Uninity $k+1$ 的树。
显然,一棵 Uninity $k$ 的树,同时也是 Uninity $k+1,k+2,k+3,\cdots$ 的
给定一棵树,求最小的 $k$ 使得这棵树是 Uninity $k$ 的。
题解:
这个过程类似点分治,所以一个上界为 $\log_2n$。但答案可能比这个小。
点分治的一个性质为:任意两个在点分树中深度相同的点,它们在原树上的路径经过的结点中,必定存在一个结点,它在点分树上的深度比那两个结点都小。其中一个结点就是分治中心。
考虑这道题,我们要给每个结点标号,并满足:
- 叶子结点标号为 $0$。
- 两个标号相等的结点的简单路径上,存在一个结点的标号比它们大。
- 标号最大的结点的标号最小。
我们对每棵子树进行贪心,状压记录所有的这个子树内出现的,并且 这个标号的结点 到当前根的路径中,不存在标号大于 这个标号的结点 的 标号。对于一个标号 $x$,如果它在至少两个子树中有记录,那么你当前结点的标号必然大于 $x$。如果它在一个子树中有记录,那么当前结点的标号就不能为 $x$(否则两个 $x$ 中间就不存在比 $x$ 大的结点,不满足条件)。我们在能取的标号中取最小的即可。可以用一系列位运算来实现。
时间复杂度 $O(n)$。
Code:
#include<iostream>
#include<vector>
#include<algorithm>
typedef unsigned u32;
using namespace std;
const int N=1e5+5;
vector<int>G[N];
int n,g[N];
u32 f[N];
void dfs(int now,int pre){
u32 x=0,y=0;
for(int to:G[now])if(to!=pre){
dfs(to,now);
x|=y&f[to],y|=f[to];
}
if(y==0)f[now]=1;else{
g[now]=__builtin_ctz(~y&(-(1u<<(32-(x?__builtin_clz(x):32)))));
f[now]=y>>g[now]<<g[now]|(1<<g[now]);
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<n;++i){
int u,v;
cin>>u>>v;
G[u].push_back(v),G[v].push_back(u);
}
dfs(1,0);
cout<<*max_element(g+1,g+n+1)<<'\n';
return 0;
}