树的点分治讲解

  在说点分治之前先说一下序列分治,序列分治大家都知道吧,就是把序列从某个位置(一般是中间点)分成两部分,统计跨越两部分的答案再递归处理两部分。树的点分治的道理和序列分治很像,但树没有中点,该怎么分治呢?再对比序列分治,序列相当于一条链,而序列的中点就是这条链的重心,那么树的分治点就可以是这棵树的重心。回顾一下重心的性质:以树的重心为根,这棵树最大子树大小不大于整棵树大小的一半。这样就可以保证时间复杂度是O(nlogn)(因为最多logn层啊qwq)。

  当以一个点为当前重心时,处理的答案是跨区域的(也就是统计任意两个子树之间经过重心产生的答案)。在这里介绍两种常用的统计方法:

1、统计所有子树中任意两点满足的方案数(不管两个点是否在同一棵子树里),再用单步容斥减掉在同一棵子树中的答案数。但这种方法的前提条件是统计答案满足逆运算(即总答案-不符合答案=符合答案)。

2、将当前子树里的点和之前遍历的子树中的所有点统计答案,这样避免了不符合的答案,也不需要满足逆运算,有的题只能用这种方法来统计。

最后说一下点分治题求解的主要过程:

1、找重心并以重心为根对子树dfs答案信息(例如路径长或者满足条件节点数)。

2、统计答案。

3、递归分治处理每棵子树。

代码时间

找重心

void getroot(int x,int fa)
{
    size[x]=1;
    mx[x]=0;
    for(int i=head[x];i;i=next[i])
    {
        if(to[i]!=fa&&!vis[to[i]])
        {
            getroot(to[i],x);
            size[x]+=size[to[i]];
            mx[x]=max(mx[x],size[to[i]]);
        }
    }
    mx[x]=max(mx[x],num-size[x]);
    if(!root||mx[x]<mx[root])
    {
        root=x;
    }
}

分治过程

void partition(int x)
{
    vis[x]=1;
    ans+=calc(x,0);
    for(int i=head[x];i;i=next[i])
    {
        if(!vis[to[i]])
        {
            ans-=calc(to[i],val[i]);
            num=size[to[i]];
            root=0;
            getroot(to[i],0);
            partition(root);
        }
    }
}

变量名解释:

size[i],i的子树大小;mx[i],以i为重心最大子树大小;num,当前子树总大小;mx[0]初始成INF。

 推荐练习题:

BZOJ1468Tree

BZOJ2152聪聪可可

BZOJ3697采药人的路径

BZOJ2599Race

BZOJ1316树上的查询

BZOJ4016最短路径树问题

BZOJ4182Shopping

BZOJ3451Normal

BZOJ3672购票

猜你喜欢

转载自www.cnblogs.com/Khada-Jhin/p/9184417.html