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

(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;iint 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;iint nx=e[p][i];
        if(nx!=to)solve(nx),del(nx);
    }
    if(to)solve(to);
    if(!p)return;
    for(int i=0;iint nx=e[p][i];
        if(nx!=to)ins(nx);
    }
    sum[dep[p]]++;
    for(int i=0,sz=ask[p].size();isum[ask[p][i].k]-1;
}

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

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

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

你可能感兴趣的:(学习总结)