讲前须知:线段树动态开点
1.概念
线段树合并,一般来说是与线段树动态开点一起用的,它的作用将两棵线段树所统计的信息整合到一棵线段树上。(由于多棵线段树会导致空间复杂度极高,所以线段树合并一般会与动态开点一起使用)
2.思想
我们在合并线段树的时候,肯定不可能一个点一个点的合并,那么我们就需要将一些无用的合并操作去掉。由于我们是动态开点,那么无用的操作指的就是,两棵线段树中有一棵未开辟的节点(或两棵都未开辟),那么直接返回已经开辟了的那一棵就可以了。代码:
int merge(int u , int v)
{
if (!u || !v)
{
return u + v;
}
int t = ++tot;//重新开点记录当前两棵树的信息
sum[t] = sum[u] + sum[v]//根据题意,这里可以有不同的写法,有时还可能记录某些答案
ls[t] = merge(ls[u] , ls[v]);
rs[t] = merge(rs[u] , rs[v]);//递归处理左右子树
return t;
}
上面这种写法适用于大多数情况,但它对于所有两棵子树共同的节点都开了一个新节点。所以当空间限制极为苛刻的时候,这种写法不一定适用。另一种写法:
void merge(int &u , int v)
{
if (!u || !v)
{
u += v;
return;
}
sum[u] += sum[v];
merge(ls[u] , ls[v]);
merge(rs[u] , rs[v]);
}
这种写法的优势是:不用重新开点,直接将u和v的信息全部合并到u上。但也因为这一点,u所保存的信息将被破坏掉,即最后无法保存合并前的某一棵线段树。所以,这种写法一般都用在树上,父亲节点合并子节点信息时。(还有,我有一次打比赛时这样写爆零了,之后怎么查都查不出来错,所以,在空间足够的情况下,建议使用前一种写法。当然,如果是我模板写的有问题,还望雅正)
3.应用
感觉我做过的大部分题都是在树上,父亲节点合并子节点信息时会用到。当然,其它很多地方应该都会用到,但我太蒻了,那些题还是交给大佬来吧。
4.习题
1.入门
板子:https://www.luogu.org/problemnew/show/P3605
2.进阶 (有一定思维难度)
https://www.luogu.org/problemnew/show/P3521 (较难)
https://www.luogu.org/problemnew/show/P3899 (做法很多)
https://www.luogu.org/problemnew/show/P3224 (较简单)
3.再进阶
http://acm.hdu.edu.cn/showproblem.php?pid=5709 (不是很好做)