原题链接
https://agc009.contest.atcoder.jp/tasks/agc009_d
Description
给你一棵n个节点的树
你需要找到一种点分治方案,使得点分树的深度最小,输出这个最小深度(根的深度为0)
n<=100000
Solution
我们将每个点标号,表示这个点在点分树中的深度
有性质:两个标号相同的点之间的路径上一定有一个标号严格小于他们的标号的点
根据点分治的流程我们容易发现这是对的
方便起见,我们把标号反过来,叶子的标号为0,标号的最大值为这棵树深度
严格小于就变成了严格大于
设 Fi,j F i , j 表示从i的子树的节点到i的路径上还有多少个标号为j的没有被消掉(即路径上还没有标号严格大于它的点)
递归处理子树,然后将各个子树的加起来
如果一旦某一个标号出现了至少两次( Fi,j>1 F i , j > 1 ),那么意味着i这个节点的标号至少是j+1(否则就有一条路径全部确定,同时又不合法)
从最大的那个j+1向后找,直到第一个 F=0 F = 0 时(否则出现不合法),那么这个点就选这个标号,然后j小于这个标号的 Fi,j F i , j 都清空(这些路径已经合法了)
所以一遍DFS就搞定了
Code
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 100005
using namespace std;
int nt[2*N],fs[N],dt[2*N],n,m,s[N],f[N][20],ans;
void link(int x,int y)
{
nt[++m]=fs[x];
dt[fs[x]=m]=y;
}
void dfs(int k,int fa)
{
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
if(p!=fa)
{
dfs(p,k);
fo(j,0,19) f[k][j]+=f[p][j];
}
}
int lim=0;
fod(j,19,0) if(f[k][j]>1)
{
lim=j+1;
break;
}
while(f[k][lim]) lim++;
s[k]=lim;
fo(j,0,lim-1) f[k][j]=0;
f[k][lim]++;
ans=max(ans,s[k]);
}
int main()
{
cin>>n;
fo(i,1,n-1)
{
int x,y;
scanf("%d%d",&x,&y);
link(x,y);
link(y,x);
}
dfs(1,0);
printf("%d",ans);
}