foreverlasting
2019-07-19 19:56:02
2024.12.21 update: fix formula.
以前曾经写过一个总结,但写得超级烂,于是再写一个。
以下都是口胡的,可能有不正确的地方请求指点 QAQ。
首先我们要理解线段树(现在指狭义的线段树)是什么。
线段树是一种容易维护区间的数据结构,是一种区间分治实体化的产物。
准确来说,比如你维护区间
那么线段树分治指什么呢?
实际上是一种维护时间区间的数据结构,同样是利用线段树的分治性,让复杂度保证在了
但是,维护时间区间的东西还有很多,比如 CDQ 分治,比如 KD-Tree,这一类数据结构都能维护时间区间,那线段树分治的特殊作用在哪里呢?实际上,一般而言,它的作用就是支持撤销操作,或者说就是维护了一个操作影响的时间区间,这是 CDQ 分治等不容易做到的,是它这种结构的特殊优势所在。
我们考虑如何维护一个操作影响的时间区间。假设一个操作影响的时间是
这样干说好像没什么用,来看几道例题。
题意:你要维护若干个集合,每个集合都是有一个编号比他小的集合扩展而来,扩展内容为加入一个新的元素
询问和操作级别都是
题解:这是一道非常经典的线段树分治的题。将树形结构建出来后,可以发现的是一个元素就是它第一次出现的点的子树中扣掉把它删除的点的子树,即可以看做一段连续的区间(dfs 序)。我们把这个东西看做影响的时间区间,丢到线段树上,考虑如何对于一个查询计算答案。我们在线段树上的每个结点维护关于
题意:有
询问和操作级别都是
题解:这也是一道经典的线段树分治的题。它与上题的区别,一是计算操作影响的方式,这题需要
题意:给出一个连通带权无向图,边有边权,要求支持
1、
2、
3、
保证任意操作后原图连通无重边自环且操作均合法
图的点数和边数级别为
题解:这也算是一道线段树分治的经典题了。这题与上面两题其实没有太大的区别,更多的是作为一道线段树分治的熟悉/练手题来做。线段树维护时间轴,将每一条边的影响区间插入到线段树上,然后把询问也插入到线段树上,递归整棵线段树就可以了。具体实现上,利用并查集和线性基,由于这里并查集无法压缩(因为并查集上的每条边维护了边权),所以按秩合并。在递归回来的时候,利用可撤销并查集的性质,撤销之前区间的影响即可。这样的
题意:给你一张
题解:这并不是一道线段树分治的经典题。这题有个难点,就是染色后的判断,判断出来后可能撤销这次操作,导致了每条边的存在时间不固定。众所周知,二分图判断可以用奇环,奇环可以利用并查集。问题是如何固定来保证正确性和复杂度的正确性。有一种优秀的做法就是利用线段树分治的思想,我们将修改操作影响的区间定义为从现在开始到下次这条边修改的时间区间,然后插入到线段树里。然后每次递归到某个时间点
下面再给出几道题:
[HAOI2017]八纵八横
[SHOI2014]神奇化合物
[APIO2018]New Home 新家
[CF678F]Lena and Queries
[bzoj4311]向量
[bzoj4184]shallot
最后给出一种模板(其实我感觉没什么用):
void insertmodify(res rt,res l,res r,res L,res R,SEG id){
if(L<=l&&R>=r){
f[rt].push_back(id);
return;
}
if(l==r)return;
res mid=(l+r)>>1;
if(L<=mid)insertmodify(rt<<1,l,mid,L,R,id);
if(mid<R)insertmodify(rt<<1|1,mid+1,r,L,R,id);
}
void insertquery(res rt,res l,res r,res p,QUE id){
g[rt].push_back(id);
if(l==r)return;
res mid=(l+r)>>1;
if(p<=mid)insertquery(rt<<1,l,mid,p,id);
else insertquery(rt<<1|1,mid+1,r,p,id);
}
void segdiv(res rt,res l,res r){
solve(f[rt],g[rt]);
if(l==r){get(l);return;}
res mid=(l+r)>>1;
segdiv(rt<<1,l,mid),segdiv(rt<<1|1,mid+1,r);
}
总结:线段树分治作为一种较为优秀的维护时间轴的数据结构,在很多功能上它或许不如 CDQ 分治这种可以缩小数据规模的数据结构,但无疑在撤销操作上它已经登峰造极了。(雾
在最后,表示上面全在口胡,有错误请各位指出(鞠躬