模板:启发式合并

首先说明一点:线段树合并不是启发式合并。

启发式合并的大概内容就是:把小的数据结构按照这个数据结构的正常插入方法,一个一个地暴力塞进去。

而线段树合并显然不是这个东西。

这道题的题解太烂了,所以耽误了很长时间。

对于每一次操作,它只有3个参数:起始位置,作用时间,颜色。

把颜色离散化一下,让它们的编号分布在1e5以内。也可以不离散化,略麻烦一些而已。注意有负数。

现在我们维护一个以时间为区间下标的线段树,里面维护两个权值,一个是第一次出现的小球的个数,另一个是所有小球的个数。

我们另开一组vector存储发生在这个节点上的操作,需要存储时间/颜色这2个参数。

刚开始我们读入所有操作,直接把它放进起始位置的vector里。

然后就可以一遍dfs求解答案。考虑在每个点要做什么事。

1)递归求解所有子树

2)求解所有在这个节点上的操作得到本节点的答案

3)把与自身相关的所有事件上传给父节点

这其实是暴力思路。

我们考虑一下,如果按照上述思路,要么就是需要清空线段树,要么就是开100000个树爆内存。

也只有第一个能考虑优化。

如果1号节点有2个儿子,其中2号节点上有99995次操作,3号节点上有5次。

还要清空线段树?有什么想法吗?

对于3号节点,它暴力上传的复杂度并不高,关键就是怎么处理2号节点。

如果先扫3号节点,清空,再2号节点,信息不清空,事件也暂时不上传。

那么回溯到了1号节点,我们有一个现成的已经存好了99995个操作的线段树,而vector里面只有5次操作需要插入。多好啊

考虑这样一个问题:因为父节点只是一个点,而子树里可能有很多点,所以某个儿子身上可能聚集了很多操作。

那么对于每一个节点,我们称其“子树中操作数最多的儿子”为它的重儿子。

那么按照刚才举例子的这个思路,优化一下上面的那个dfs流程。

1)扫所有的轻儿子,把轻儿子的vector中的全部时间放进父节点额vector里,清空线段树。

2)求解重儿子,保留线段树,暂时不加入重儿子vector里面的事件。

3)把vector里面所有的为插入线段树的事件插入线段树,求解答案。

然而我的代码实现把清空和信息上传放在了dfs的最后,为了代码与题解统一,稍微改一下。

1)扫轻儿子。

2)扫重儿子。

3)把vector里面所有的为插入线段树的事件插入线段树,求解答案。

4)如果当前节点不是它父节点的重儿子,就清空线段树,把vector上传。

其实本质上是一样的,个人感觉我这个挺好理解。

然而上面这个思路仍然不够成熟,有一些具体实现会让时间复杂度产生一些差别。

首先是如何求出重儿子。这个还是比较简单的,dfs扫一遍。记得打完这个函数要调用!不要像某个姓吴的名字是两个字的傻子一样。

1 void pre_dfs(int p,int fa){
2     siz[p]=v[p].size();
3     for(int i=fir[p];i;i=l[i])if(to[i]!=fa){
4         pre_dfs(to[i],p),siz[p]+=siz[to[i]];
5         if(siz[to[i]]>siz[hson[p]])ihs[hson[p]]=0,hson[p]=to[i],ihs[to[i]]=1;//hson[p]存p的重儿子,ihs[p]表示p是否为父节点的重儿子
6     }
7 }

然后是怎么把vector里面的数据上传。考虑极端情况:一条100000点的链,在最后一个点上有100000个事件。

显然暴力一个一个事件地上传会T成狗,这就是为什么要启发式合并,这样就有了nlog的合并复杂度的保证了。

1 void up(int p,int fa){
2     if(v[ref[p]].size()<v[ref[fa]].size()){//把小的往大的里面逐个插入
3         for(int i=0;i<v[ref[p]].size();++i)v[ref[fa]].push_back(v[ref[p]][i]);
4         v[ref[p]].clear();//清空,防止爆内存,但是好像不必要
5     }
6     else{for(int i=0;i<v[ref[fa]].size();++i)v[ref[p]].push_back(v[ref[fa]][i]);v[ref[fa]].clear();ref[fa]=ref[p];}
    //如果是父节点的vector比儿子小,那么把父亲节点的vector倒进子节点的之后,要把代表元素修改一下,即节点fa对应的vector不再是v[fa]而是v[p]
7 }

再接下来需要实现的就是线段树了。设t数组表示球的总数量,c数组表示有贡献的球的数量(即每个颜色第一次出现的球的数量)

首先是修改,为了以后还要清空线段树(具体的等会再说),需要可加可减

1 void insert(int p,int pos,int tt,int cc){
2     if(cl[p]==cr[p]){t[p]+=tt;c[p]+=cc;return;}
3     if(pos<=cr[p<<1])insert(p<<1,pos,tt,cc);
4     else insert(p<<1|1,pos,tt,cc);
5     t[p]=t[p<<1]+t[p<<1|1];c[p]=c[p<<1]+c[p<<1|1];
6 }

猜你喜欢

转载自www.cnblogs.com/hzoi-DeepinC/p/11270149.html