[联合集训6-19] 山洞 点分树

一句话题意就是求点分树最小深度。
点分树有一个性质:我们称点 i 在点分树上距叶子的距离为其权值 w i ,那么对于两个点 u , v 满足 w u = w v = k ,在原树路径 ( u , v ) 上一定存在点 t 使得 w t > k ,证明很显然。
我们对每个点 i 求出一个二进制状态,二进制第 k 位表示该点子树中存不存在一个 w j = k 的点 j ,且 ( i , j ) 路径上没有 w > k 的点,因为点分树的深度是 O ( log ) 的,这个二进制状态不会很大。假设我们已知 i 所有儿子的二进制状态,那么 w i 的取值就受到两个限制:
1. 如果存在两个儿子第 k 位同为 1 ,那么 w i > k
2. 如果存在某个儿子第 k 位为 1 ,那么 w i k
在满足这两个性质的前提下, w i 取合法的最小值(至于为什么取最小的最优不太会证。。。但感觉挺显然的),之后 i 点的二进制状态就是其所有儿子状态或和,再把 w i 位赋成 1 并把 w i 1 一下的位赋为 0 即可。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100010
using namespace std;
int n,tote,to[N<<1],nxt[N<<1],con[N],w[N],ans;
void ins(int x,int y)
{
    to[++tote]=y;
    nxt[tote]=con[x];
    con[x]=tote;
}
void dfs(int v,int fa)
{
    int dep=-1;
    bool flag=0;
    for(int p=con[v];p;p=nxt[p])
        if(to[p]!=fa)
        {
            dfs(to[p],v);
            int tmp=w[v]&w[to[p]];
            for(int i=30;i>=0;i--)
                if((tmp>>i)&1) {dep=max(dep,i);break;}
            w[v]=w[v]|w[to[p]];
            flag=1;
        }   
    if(!flag) {w[v]=1;return ;} 
    for(int i=dep+1;i<=30;i++)
        if((w[v]>>i)&1) 
            dep=max(dep,i);
        else break;
    ans=max(ans,dep+1);
    int R=(1<<(dep+1)),T=((1<<30)-1)^(R-1);
    w[v]=(w[v]&T)|R;    
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        ins(x,y);ins(y,x);
    }
    dfs(1,0);
    printf("%d",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/dofypxy/article/details/80766480