murder_drones
2024-11-07 09:02:48
这个 trick 的来源是一篇 CF 上的博客,本文进行了部分翻译与一定的拓展。
FHQ 的合并有一个痛点:必须保证值域不交。但是本文给出了一种均摊分析方式,使得可以维护随意合并,分裂的 FHQ。
UPD 2024/11/15:文末资料处有一篇别人写的很详细的同样内容,一起食用效果更佳。
初始有若干棵 FHQ treap ,合并时将两个 FHQ 拆成若干值域不交的段,然后合并。写成代码是这样的。
int tr_merge(int x,int y){
int z=0;
while(x&&y){
long long vx=get_min(x),vy=get_min(y);
if(vx>vy) swap(vx,vy),swap(x,y);
int w;split_by_val(x,vy,w,x);
z=merge(z,w);
}
z=merge(z,x);z=merge(z,y);
return z;
}
UPD 2024/11/15:这里应当要合并完全相同的节点,但是由于一般情况下每个点的key值都不同,所以也仍然正确,可以不用特别处理。
分裂的代码则不变,与正常的 FHQ 没有不同。
对于一个有序序列
借用一下 CF 老哥的图,接下来展开对各操作的分析。
假设此时要合并
注意到同一值域段内由于间距为
进行放缩:
由
推一下:
即
将其应用回式子进行一加一减:
也就是说,每次合并两个颜色段为
以下给出一些例题,但是都不是正解,所以仅供参考。
容易发现做的操作一模一样,直接维护即可,但是由于询问过多,无法通过本题。但是复杂度中没有
代码
考虑将一些左/右端点相同的线段看做一组,每一组用一棵 FHQ 维护该组的长度集合,容易发现一次激光会将左右两边的组进行合并 (1),以及将其穿过的组进行分割 (2)。图示如下。
红色要并到线上;紫色也要并到线上;绿色要分成一组不动,一组并线;蓝色不变。
求解答案只需要在移动的过程中给整棵树打加法 tag 即可。
现在来分析一下时间复杂度。
先分析分裂操作,初始有 n 组,我们发现在任意时刻,如果暂时去掉那些从最开始就没被做过任何操作的线段,你没有办法构造出两个梯形的斜边所在区间相交。如图所示,你没法去掉没动过的线段后还保留以下任意两个相交的梯形。这个可以由详尽分讨证明,在此不再赘述。
于是分裂操作顶多
现在考虑合并,发现由于分裂操作有保证,每次合并段数减少,分裂段数增加,于是容易分析出合并次数也是
于是我们得到了总复杂度为
代码
CF原博客链接:"Merging treaps" -- or how to merge sorted sets in good complexity...
一篇别人写的更加详细且有例题的文章:P5494 题解 & 平衡树合并