详解点分治

详解点分治

本篇随笔讲解算法竞赛中的点分治淀粉质算法。


一、点分治概述及应用

点分治是一种在树上进行路径静态统计的算法。

所谓树上的静态统计,并不是像树剖一样维护路径最值、路径和之类的统计。那样的话,这个算法的存在就没什么意义了。

来上道小题体会一下点分治的解决问题。

给定一棵有n个节点的带边权无根树。求长度不超过k的路径有多少条。

看懂了么?就是这种有限制的路径统计问题,非常适合用点分治解决。


二、点分治的基本实现思路

点分治点分治,最终还是落回分治这个东西。分治是什么?其原理就是把原问题拆解成几个子问题,分别求解子问题之后再合并出原问题。那么点分治其实就是对一棵树进行拆开,分治。

那么用上面的问题作为引入。

显然,暴力的方式是枚举所有路径,然后判断是否合法。但是所有的路径都可以分两种情况:第一种,过根节点。第二种,不过根节点。

那么我们把根节点断掉,又形成了很多新子树,对于这些子树,仍然会对它们的路径分这两种情况......

可以看出,所有不过整棵树根节点的路径,必会在这种不断拆解形成新子树的过程中,过一遍根节点。

对其有感觉么?这就是递归的过程么。

于是点分治实现的大致思路就是:

1、任取一个点作为无根树根节点。

2、计算所有第一种路径。

3、将当前根节点断掉,递归计算下一层子树。步骤同上。


三、点分治的时空复杂度分析

点分治过程中,分治点的选择至关重要。

一个递归层的时间复杂度是\(O(n\log n)\)

极端情况下,树会退化成链,那么对于链,我们最不划算的选择分治点方式是每次选择链头,那么就会变成递归n次,总复杂度是\(O(n^2\log n)\)

那么我们每次选择链的中心呢?

很简单,递归变成了log次,总复杂度就是\(O(n\log^2 n)\)

那么,对于链是这样选择,对于树也是这样选择。选”中点“当然是最划算的。那么树的“中点”是什么呢?

即,树的重心。

重心有一个性质:那就是删除重心后,树被拆成若干个部分,这些部分中,不会有任何一个部分超过总点数的二分之一。否则就不符合重心的性质。

所以,每次选择重心作为分治点,就保证了整个点分治算法的时间是\(O(n\log^2 n)\)


四、点分治的代码实现

根据第二部分的思路概述,我们应该很容易用递归方式写出其框架代码:

void dfz(int x)//大多数人叫divide,我就喜欢这样
{
	v[x]=1;//避免重复选择绕死
    calc();//注意!点分治的精华!
    for(int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(v[y])
        	continue;
       	sum=size[y],root=0;//找子树重心的前置信息
        getroot(y,0);//这波是找子树重心
        dfz(root);//套娃递归
    }
}

其中的getroot函数,功能是找子树重心。根据重心的定义及之前学过的求法,应该这么写:

void getroot(int x,int f)
{
	size[x]=1,mp[x]=0;
    for(int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(y==f||v[y])
            continue;
        getroot(y,x);
        size[x]+=size[y];
        mp[x]=max(mp[x],size[y]);
    }
    mp[x]=max(mp[x],sum-size[y]);
    if(mp[x]<mp[root])
        root=x;
}

只有这两个能算作模板化的东西。

然而点分治的精华,calc函数部分,需要按题目的意思来求。其功能就是维护题目想要维护的信息。

这个就需要自己通过例题和练习细细体会。

posted @ 2020-10-15 19:36  Seaway-Fu  阅读(1096)  评论(1编辑  收藏  举报