跳到主要内容

线段树

参考资料

简介

线段树(Segment Tree)把区间递归二分,每个节点维护对应子区间的聚合信息(和、最值等),支持 O(logn)O(\log n) 的区间修改与区间查询。修改与查询都把目标区间拆成 O(logn)O(\log n) 个节点区间处理。区间修改用 懒标记(lazy tag)延迟下放:先把整段更新记在节点上,待访问子节点前再下传,避免逐元素修改。

实现

773 Bcpp
struct SEG
{
ll a[N],val[N<<2],tag[N<<2];
void gx(int u,ll v,int len){val[u]+=v*len;tag[u]+=v;}
void push_up(int u){val[u]=val[ls]+val[rs];}
void push_down(int u,int l,int r)
{
gx(ls,tag[u],mid-l+1);
gx(rs,tag[u],r-mid);
tag[u]=0;
}
void build(int u,int l,int r)
{
if(l==r){val[u]=a[l];return;}
build(ls,l,mid);
build(rs,mid+1,r);
push_up(u);
}
void update(int u,int l,int r,int x,int y,ll v)
{
if(x<=l&&r<=y){gx(u,v,r-l+1);return;}
push_down(u,l,r);
if(x<=mid)update(ls,l,mid,x,y,v);
if(y>mid)update(rs,mid+1,r,x,y,v);
push_up(u);
}
ll query(int u,int l,int r,int x,int y)
{
if(x<=l&&r<=y)return val[u];
push_down(u,l,r);
ll res=0;
if(x<=mid)res+=query(ls,l,mid,x,y);
if(y>mid)res+=query(rs,mid+1,r,x,y);
return res;
}
};

线段树合并 & 分裂

在动态开点的权值线段树上,线段树合并 把两棵树对应位置的节点递归合并成一棵:某一侧为空时直接返回另一侧,否则递归合并左右子树并上传。

合并的总复杂度与被合并掉的节点数同阶,因此把 nn 棵单点线段树两两合并的总代价为 O(nlogn)O(n\log n)。配合 线段树分裂(按排名或值域拆出一棵子树)可支持更灵活的操作,常与树上差分结合处理路径加、子树查询等离线问题。

李超线段树

李超线段树 维护一组一次函数(线段),支持插入一条线段、查询某个横坐标处所有线段取值的最大(或最小)值。

每个节点记录「在该区间中点处最优」的那条线段;插入时与节点上已有线段比较,把较劣的一条递归下放到它仍可能更优的半区间。插入复杂度 O(log2n)O(\log^2 n),单点查询 O(logn)O(\log n)。它常用作斜率优化的替代,尤其当斜率与查询点都不单调时。

区间最值操作 & 区间历史最值

吉司机线段树(Segment Tree Beats)支持区间取 min\min / 取 max\max、区间历史最值等「非简单」区间操作。每个节点维护区间最大值、严格次大值与最大值的个数:区间对 vvmin\min 时,若 vv 落在次大值与最大值之间,只需更新最大值这一档,否则递归处理。均摊复杂度为 O(nlog2n)O(n\log^2 n)

例题

如题,已知一个数列 {ai}\{a_i\},你需要进行下面两种操作:

  1. 将某区间每一个数加上 kk
  2. 求出某区间每一个数的和。

如题,已知一个长度为 nn 的数列 {ai}\{a_i\}1in1\leq i\leq n),初始时 aa 序列满足 ai=ia_i=i。你需要进行下面两种操作:

  1. 将某区间每一个数加上 kk
  2. 求出某区间每一个数的和。

如题,已知一个数列 aa,你需要进行下面三种操作:

  • 将某区间每一个数乘上 xx
  • 将某区间每一个数加上 xx
  • 求出某区间每一个数的和。

小豆现在有一个数 xx,初始值为 11。小豆有 QQ 次操作,操作有两种类型:

1 m:将 xx 变为 x×mx\times m,并输出 xmodMx\bmod M

2 pos:将 xx 变为 xx 除以第 pospos 次操作所乘的数(保证第 pospos 次操作一定为类型 1,对于每一个类型 1 的操作至多会被除一次),并输出 xmodMx\bmod M

第一行包含两个正整数 N,MN,M,分别表示数列中实数的个数和操作的个数。

第二行包含 NN 个实数,其中第 ii 个实数表示数列的第 ii 项。

接下来 MM 行,每行为一条操作,格式为以下三种之一:

操作 111 x y k,表示将第 xx 到第 yy 项每项加上 kkkk 为一实数。 操作 222 x y,表示求出第 xx 到第 yy 项这一子数列的平均数。 操作 333 x y,表示求出第 xx 到第 yy 项这一子数列的方差。

村落里一共有 nn 座房屋,并形成一个树状结构。然后救济粮分 mm 次发放,每次选择两个房屋 (x,y)(x, y),然后对于 xxyy 的路径上(含 xxyy)每座房子里发放一袋 zz 类型的救济粮。

然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。

nn 个点、源点 ss,初始无边。qq 次操作,每次以边权 ww 加边,共三种:点 vv 向点 uu 连边、点 vv 向区间 [l,r][l,r] 内每个点连边、区间 [l,r][l,r] 内每个点向点 vv 连边。求 ss 到每个点的最短路,不可达输出 1-1

在平面上在线维护若干条线段,支持两种操作:加入一条线段;查询所有线段中,在给定横坐标处纵坐标最大的线段编号。

维护一个序列,支持区间加、区间对 vvmin\min、区间求和、区间最大值、区间历史最大值五种操作。