这题好神啊,就是没想到还可以这样做……
问题就是将树进行点分治,最小深度是多少。显然答案的上界是logn,但是具体是多少呢?
尝试了若干贪心策略,什么直径啦,什么点数啦……好像都有反例,或者根本不知道对不对。结果看了题解才知道可以换成另一种问题去做:实际上就是给树填入数字(数字就是题目中的那个等级),需要保证两个相同数字的节点间的路径上至少存在一个比它们更大的数字,问最大的数字最小是多少。
我们从树的底部往上依次填,考虑x的子树已经全部填完,那么x应该填什么?我们应该关心在x的下方有哪些数字是危险的(即一路都没有遇到更大的节点来阻碍它,那么它可能和x冲突),记used[x][i]表示x的子树里面危险的数字i的数量。发现如果x有两个儿子都存在相同的危险数字j,那么a[x]至少大于j,否则这两个数字之间就形成冲突。除此之外,a[x]不能等于任何危险数字。于是我们得出哪些a[x]合法。我们贪心地选择最小的a[x]填进去,然后将<a[x]的危险数字标记消除(安全了)即可。至于贪心为什么是对的,我也不太会严格证明啊,总之画画看,很有道理就对了(你想,数字越小,能阻碍它的数字就越多,而且对答案影响小,等等,各种好处都有啊)。
复杂度O(nlogn)
#include <cstdio>
#include <algorithm>
#define rep(i,j,k) for (i=j;i<=k;i++)
#define down(i,j,k) for (i=j;i>=k;i--)
using namespace std;
const int N=1e5+5,K=20;
int n,i,j,u,v,ans;
int En,used[N][K],fst[N],nxt[N*2],to[N*2];
void add(int u,int v) { En++; nxt[En]=fst[u]; fst[u]=En; to[En]=v; }
void dfs(int x,int fa)
{
int i,j,v,g;
for (j=fst[x];j;j=nxt[j])
if (to[j]!=fa)
{
v=to[j];
dfs(v,x);
rep(i,0,K-1)
used[x][i]+=used[v][i];
}
down(g,K-1,0)
if (used[x][g]>1) break;
g++;
while (used[x][g]) g++;
used[x][g]=1;
rep(i,0,g-1) used[x][i]=0;
ans=max(ans,g);
}
int main()
{
// freopen("uninity.in","r",stdin);
// freopen("uninity.out","w",stdout);
scanf("%d",&n);
rep(i,1,n-1)
{
scanf("%d%d",&u,&v);
add(u,v); add(v,u);
}
dfs(1,1);
printf("%d\n",ans);
return 0;
}