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

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

代码:

#include
#include
#include
#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;iint x,y;
        scanf("%d%d",&x,&y);
        ins(x,y);ins(y,x);
    }
    dfs(1,0);
    printf("%d",ans);
    return 0;
}

你可能感兴趣的:(dp,状态压缩,树分治)