学习总结:Dsu on tree 树上启发式合并

来源:互联网 发布:ftp传输数据的具体方式 编辑:程序博客网 时间:2024/05/21 20:12

(RT,这只是一篇小小的总结,以便将来的回顾,并不详细讲)

以前也学习过启发式合并,大概就是像树形dp一样在dfs上,将儿子的信息向父亲转移,容器是map,将儿子的信息边转移边更新答案,转移之后便将儿子的容器清空,防止空间超限。不过对于本人而言,虽然思路较为简便,但是因为有用到map的迭代器,所以这种写法写起来较为繁琐。

最近学了一种基于dfs序的一种的启发式合并,特点就是:暴力。
看似暴力。

算法的流程大概是这样:
1、dfs将一棵树建好,将节点的size、dfs序、重儿子、该dfs序对应的节点这些信息处理好(其他的信息具体问题具体分析)。
2、进入solve函数,先去解决非重儿子,然后将这些非重儿子的信息暴力清空。
3、接下来解决重儿子,这次不清空。
4、然后再将非重儿子的信息再暴力添加。
5、将该节点的信息添加进容器。
6、回答关于这个节点的问题。
7、返回。
注意:这里的添加是利用dfs序进行的。

流程还是有点繁琐,那就看代码吧。

void build(int p,int pre,int d){    sz[p]=1;    dep[p]=d;    l[p]=++dfn;    f[dfn]=p;    for(int i=0;i<e[p].size();i++){        int v=e[p][i];        build(v,p,d+1);        sz[p]+=sz[v];        if(sz[v]>sz[son[p]])son[p]=v;    }    r[p]=dfn;}void del(int p){    for(int i=l[p];i<=r[p];i++)sum[dep[f[i]]]--;}void ins(int p){    for(int i=l[p];i<=r[p];i++)sum[dep[f[i]]]++;}void solve(int p){    int to=son[p];    for(int i=0;i<e[p].size();i++){        int nx=e[p][i];        if(nx!=to)solve(nx),del(nx);    }    if(to)solve(to);    if(!p)return;    for(int i=0;i<e[p].size();i++){        int nx=e[p][i];        if(nx!=to)ins(nx);    }    sum[dep[p]]++;    for(int i=0,sz=ask[p].size();i<sz;i++)ans[ask[p][i].id]=sum[ask[p][i].k]-1;}

相信看到代码的时候就在想,这不是暴力吗?
这其实和普通的启发式合并的核心是一样的:普通的启发式合并的复杂度是O(nlogn),是因为每次合并是将小集合并到大集合,对于小集合来说,合并后,集合的size至少扩大到原来的2倍,那么合并的次数一定小于等于logn。然后看有n个点,所以所有点合并的次数就是nlogn。考虑在map合并自带的复杂度,以及清空内存的复杂度,这样近似暴力的启发式合并的复杂度也就近似O(nlogn)了。
同理,每一个重儿子合并的次数也是logn。
所以Dsu on tree的复杂度同样是近似O(nlogn)。

当然对于存信息的sum不一定是普通的数组,可能是map,可能是树状数组这类既支持插入又支持删除的容器,有适合的数据结构,而不是用数据结构去套题。

这种算法能够解决关于询问一棵树的子树的相关信息的问题。
当然,算法是好的,关键是在足够理解的基础上能够用起来!

4 1
原创粉丝点击