线段树
钟神说要讲一天线段树,就很炸裂
开头钟神的一条寄语:
不爆int千万不要开#define int long long这种东西
不然有时可能会亖的很惨
——钟皓曦
//a = b+c*d;
a = (b + 1ll * c * d) % mo;//乘法转long long
//a = b*c*d + e*f + g;
a = (1ll * b * c % mo * d + 1ll * e * f + g) % mo;//三次方会爆long long,所以在里面也要取模
//a = a + b;
a += b; if (a >= mo) a-=mo;//比取模快很多
//a = b*c-d*e
a = ((1ll * b * c - 1ll * d * e) % mo + mo) % mo;//c++对负数取模还是负数,所以要把模数再加上
//a = a - b
a -= b; if (a<0) a += mo;//取模很慢
正题:
线段树是基于分治思想的二叉树,用来维护区间信息(区间和,区间最值,区间GCD等)
可以在 \(\log n\) 的时间内执行区间修改和区间查询。
线段树中每个叶子节点存储元素本身,非叶子节点存储区间内元素的统计值。
以下为一个线段树的建立例子:
(1) 用分治法自顶向下建立,每次建立,左右子树各一半
(2) 每个节点都表示一个"线段"区间,非叶子节点包含多个元素,叶子节点只有一个元素
(3) 除最后一层外,上面的每一层都是满的、
(4) 节点的数量不超过 \(4\times n - 1\) --> 数组开四倍
在线段树的存储上采用的是二叉树的存储方式
(1) 根节点为\(1\)号节点
(2) 以 \(i\) 为编号的儿子节点编号为 \(2 \times i\),\(2 \times i + 1\)
但是通常以 i << 1 和 i << 1 | 1 来表示
对于线段树需要掌握的操作为:
(1) pushup()
(2) pushdown()
(3) build()
(4) query()
(5) update() / modify()
在线段树的修改操作里 有单点修改 和 区间修改(难)
特别的对区间修改进行补充说明:
例如,对某个区间内的每个数加上\(5\),
如果修区间 \([x,y]\)所覆盖的每个叶子节点,时间将是\(O(n)\)的
我们做懒惰修改,当\([x,y]\)完全覆盖节点区间\([a,b]\)时,
先修改该区间的 sum 值,再打上一个 "懒标记" 然后立即返回。
等下次需要时,再下传 "懒标记"。这样,可以把每次修改和查询的时间都控制到$ O(\log n)$
线段树结构体:
struct node{
//定义变量
}tr[maxn*4]
模板伪代码:
void build(int u,int l,int r){
tr[u]={l,r};
if(l==r) return;
int mid=l+r>>1;
build(u<<1,l,mid);
bulid(u<<1|1,mid+1,r);
}
int query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].v;
int mid=tr[u].l+tr[u].r>>1;
int v=0;
if(l<=mid) query(u<<1,l,r);
if(r>mid) v=max(v,query(u<<1|1,l,r));
return v;
}
void update(int u,int x,int v){
if(tr[u].l==x&&tr[u].r==x) tr[u].v=v;
else{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u);
}
}
如果感觉u<<1,u<<1|1,tr[u].l,tr[u].r用的太多打字太累,可以直接用下面的宏定义:
#define ls u<<1
#define rs u<<1|1
#define lt tr[u].l
#define rt tr[u].r
(注:pushup和pushdown不定,没有具体模板)
洛谷线段树模板传送门: