原理:
线段树是一颗平衡二叉树,它的节点储存一个线段以及其它的一些信息,如图所示。
树的存储:
树的存储采用堆式存法,即存在数组里,对于节点u来说:
u << 1 是u的左儿子
u << 1 | 1是u的右儿子
u >> 1 是u的父亲
操作一,pushup操作
pushup操作是由子节点计算父节点的操作,比如父节点区间内的最大值可以由左右儿子的最大值取max
void pushup(int u){
tr[u].maxx = tr[u << 1].maxx + tr[u << 1 | 1].maxx;
}
操作二,build操作
build函数用于建造线段树,采用递归的方法,把每一个节点线段的范围以及一些初始值赋给线段树。
void build(int u,int l,int r){//u,l,r分别代表当前节点编号,和当前区间的范围
if(l == r)tr[u] = {l,r,w[l],0};//如果是叶节点就直接赋值
else{
tr[u].r = r,tr[u].l = l;//非叶节点则先赋区间值
ll mid = l + r >> 1;//取区间中点
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);//递归构建左右儿子
pushup(u);//pushup操作计算父节点其余的信息
}
}
操作三,modify操作
modify就是用于修改线段树的某一个区间或点值。
void modify(int u,int x,int v){
if(Tree[u].l == x && Tree[u].r == x)Tree[u].v = v;
//如果当前修改的点是x就直接修改
else{
int mid = Tree[u].r + Tree[u].l >> 1;
if(x <= mid)
modify(u << 1,x,v);//如果x在左区间就递归左区间
else
modify(u << 1 | 1,x,v);//否则递归右区间
push_up(u);//更新一下当前区间
}
}
操作四,query操作
query用于查询某一段区间的信息,下面为求某一区间最大值的例子。
int query(int u,int l,int r){
if(Tree[u].l >= l && Tree[u].r <= r)return Tree[u].v;
//当前的区间被包含就直接返回信息
int v = 0,mid = Tree[u].l + Tree[u].r >> 1;
if(l <= mid)
v = query(u << 1,l,r);//如果左孩子有交集就算一下左孩子
if(r > mid)
v = max(v,query(u << 1 | 1,l,r));//右孩子也同理
return v;
}
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
const ll maxn = 1e6 + 5;
const ll mod = 1e9 + 7;
struct node
{
ll l,r,v;
}Tree[maxn];
void push_up(int u){
Tree[u].v = max(Tree[u << 1].v,Tree[u << 1 | 1].v);
}
void build(int u,int l,int r){
Tree[u].l = l,Tree[u].r = r;
if(l == r)return;
int mid = l + r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
}
int query(int u,int l,int r){
if(Tree[u].l >= l && Tree[u].r <= r)return Tree[u].v;
int v = 0,mid = Tree[u].l + Tree[u].r >> 1;
if(l <= mid)
v = query(u << 1,l,r);
if(r > mid)
v = max(v,query(u << 1 | 1,l,r));
return v;
}
void modify(int u,int x,int v){
if(Tree[u].l == x && Tree[u].r == x)Tree[u].v = v;
else{
int mid = Tree[u].r + Tree[u].l >> 1;
if(x <= mid)
modify(u << 1,x,v);
else
modify(u << 1 | 1,x,v);
push_up(u);
}
}
int main()
{
ios::sync_with_stdio(false);
int m,p,lst = 0,n = 1,a,b;
char str[3];
cin >> m >> p;
build(1,1,m);
while(m--){
cin >> str;
if(str[0] == 'Q'){
cin >> a;
lst = query(1,n - a,n - 1);
cout << lst << endl;
}else{
cin >> a;
modify(1,n,(lst + a) % p);
n++;
}
}
return 0;
}
操作五,pushdown操作
pushdown操作是和pushup操作截然不同的操作,他是由父节点计算子节点,与线段树的区间和懒标记有关。
void pushdown(int u){
node < = tr[u << 1],&rt = tr[u << 1 | 1];
if(tr[u].add){
lt.add += tr[u].add;
rt.add += tr[u].add;
lt.sum += tr[u].add * (lt.r - lt.l + 1);
rt.sum += tr[u].add * (rt.r - rt.l + 1);
tr[u].add = 0;
}
}
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define eps 1e-8
#define pb push_back
#define endl '\n'
using namespace std;
const ll maxn = 2e6 + 5;
ll n,m,w[maxn],a,b,c;
char str[maxn];
struct node
{
ll l,r,sum,add;
}tr[maxn];
void pushup(int u){
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u){
node < = tr[u << 1],&rt = tr[u << 1 | 1];
if(tr[u].add){
lt.add += tr[u].add;
rt.add += tr[u].add;
lt.sum += tr[u].add * (lt.r - lt.l + 1);
rt.sum += tr[u].add * (rt.r - rt.l + 1);
tr[u].add = 0;
}
}
void build(int u,int l,int r){
if(l == r)tr[u] = {l,r,w[l],0};
else{
tr[u].r = r,tr[u].l = l;
ll mid = l + r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
void modify(int u,int l,int r,int x){
if(tr[u].l >= l && tr[u].r <= r){
tr[u].sum += x * (tr[u].r - tr[u].l + 1);
tr[u].add += x;
}else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid)modify(u << 1,l,r,x);
if(r > mid)modify(u << 1 | 1,l,r,x);
pushup(u);
}
}
ll query(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r)return tr[u].sum;
else{
pushdown(u);
ll mid = tr[u].r + tr[u].l >> 1;
ll sum = 0;
if(l <= mid)sum = query(u << 1,l,r);
if(r > mid)sum += query(u << 1 | 1,l,r);
return sum;
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> w[i];
build(1,1,n);
while(m--){
cin >> str;
if(str[0] == 'Q'){
cin >> a >> b;
cout << query(1,a,b) << endl;
}else{
cin >> a >> b >> c;
modify(1,a,b,c);
}
}
return 0;
}
懒标记:
懒标记比较重要,在修改区间时,我们仿照查询操作,如果当前区间已经被要修改的区间包括时,我们就停止继续修改,而是在当前区间打上标记,这个标记意味着在下次查询或者修改时需要进行pushdown操作,ps:pushdown操作通常放在最前面,懒标记可以叠加不可以替换
易错的地方:
就个人来说,线段树经常在build函数和query出错build经常忘记赋值区间,query函数经常范围查找错,一旦发生错误就会re,线段树的数组通常要开四倍的数组