线段树对编号连续的点构成的线段进行修改和统计操作,必须要求复合的是区间加法。
所谓区间加法有这样几个例子:
总数字之和 == 左区间之和 + 右区间之和
最大值 == max(左区间最大值,右区间最大值)
最大公因数 == GCD(左区间最大公因数,右区间最大公因数)
…………
线段树的操作主要由“点值的修改”、“区间的查询”、“区间的修改”、“上推下放函数”。
定义部分:
#define Max 10005
int sum[Max * 4]; //区间和存储数组
int mark[Max * 4]; //mark标记数组,表示区间的mark标记
int ar[Max]; //用来存原数组的下标
上推下推函数:
//上推
void pushup(int pn){
sum[pn] = sum[pn * 2] + sum[pn*2 + 1]; //更新节点信息求和
}
//下推
void pushdown(int lsn, int rsn, int pn){ //lsn, rsn是左右子树的数字个数,pn是当前节点编号
if(mark[pn]){
mark[pn * 2] += mark[pn];
mark[pn*2 + 1] += mark[pn];
sum[pn * 2] += mark[pn * 2] * lsn;
sum[pn*2 + 1] += mark[pn*2 + 1] * rsn;
mark[pn] = 0;
}
}
建树:
//建树
void build(int l, int r, int pn){ //[l, r]表示当前操作区间,pn表示当前操作的节点编号
if(l == r){ //如果到达叶节点就储存数组值
sum[l] = ar[l];
return ;
}
int m = (l + r) / 2;
build(l, m, pn * 2); //左建树
build(m + 1, r, pn*2 + 1); //右建树
pushup(pn); //全都更新求和
}
节点值修改:
//修改节点的值
void poi_update(int pos, int num, int l, int r, int pn){ //pos代表要修改的那个节点的位置,num是要加上的数,[l, r]是当前操作区间,pn是当前节点编号
if(l == r){
sum[l] += num;
return ;
}
int m = (l + r) / 2;
if(pos <= m){
poi_update(pos, num, l, m, pn * 2);
}
else{
poi_update(pos, num, m + 1, r, pn*2 + 1);
}
pushup(pn);
}
区间修改:
//区间的修改
void edge_update(int L, int R, int num, int l, int r, int pn){ //[L, R]表示操作区间,[l, r]表示当前操作区间,num是要改变的值,pn是当前位置
if(L <= l && r <= R){ //如果在操作区间内就改变,并且留下mark标记
sum[pn] += num * (l - r + 1);
mark[pn] += num;
return ;
}
int m = (l + r) / 2;
//pushdown(m - l + 1, r - m, pn);
if(L <= m){
edge_update(L, R, num, l, m, pn * 2);
}
if(m < R){
edge_update(L, R, num, m + 1, r, pn*2 + 1);
}
pushup(pn);
}
区间查找:
//查找
int srch(int L, int R, int l, int r, int pn){ //L, R表示操作区间,l, r表示当前区间,pn表示当前位置
if(L <= l && r <= R){
return sum[pn]; //在操作区间内的就返回
}
int m = (l + r) / 2;
pushdown(m - l + 1, r - m, pn); //下放
int ans = 0;
if(L <= m){
ans += srch(L, R, l, m, pn * 2);
}
if(R > m){
ans += srch(L, R, m + 1, r, pn*2 + 1);
}
return ans;
}
完整一般数组代码:
#include <cstdio>
#include <iostream>
#define Max 10005
using namespace std;
int sum[Max * 4]; //区间和存储数组
int mark[Max * 4]; //mark标记数组,表示区间的mark标记
int ar[Max]; //用来存原数组的下标
//上推
void pushup(int pn){
sum[pn] = sum[pn * 2] + sum[pn*2 + 1]; //更新节点信息求和
}
//下推
void pushdown(int lsn, int rsn, int pn){ //lsn, rsn是左右子树的数字个数,pn是当前节点编号
if(mark[pn]){
mark[pn * 2] += mark[pn];
mark[pn*2 + 1] += mark[pn];
sum[pn * 2] += mark[pn * 2] * lsn;
sum[pn*2 + 1] += mark[pn*2 + 1] * rsn;
mark[pn] = 0;
}
}
//建树
void build(int l, int r, int pn){ //[l, r]表示当前操作区间,pn表示当前操作的节点编号
if(l == r){ //如果到达叶节点就储存数组值
sum[l] = ar[l];
return ;
}
int m = (l + r) / 2;
build(l, m, pn * 2); //左建树
build(m + 1, r, pn*2 + 1); //右建树
pushup(pn); //全都更新求和
}
//修改节点的值
void poi_update(int pos, int num, int l, int r, int pn){ //pos代表要修改的那个节点的位置,num是要加上的数,[l, r]是当前操作区间,pn是当前节点编号
if(l == r){
sum[l] += num;
return ;
}
int m = (l + r) / 2;
if(pos <= m){
poi_update(pos, num, l, m, pn * 2);
}
else{
poi_update(pos, num, m + 1, r, pn*2 + 1);
}
pushup(pn);
}
//区间的修改
void edge_update(int L, int R, int num, int l, int r, int pn){ //[L, R]表示操作区间,[l, r]表示当前操作区间,num是要改变的值,pn是当前位置
if(L <= l && r <= R){ //如果在操作区间内就改变,并且留下mark标记
sum[pn] += num * (l - r + 1);
mark[pn] += num;
return ;
}
int m = (l + r) / 2;
//pushdown(m - l + 1, r - m, pn);
if(L <= m){
edge_update(L, R, num, l, m, pn * 2);
}
if(m < R){
edge_update(L, R, num, m + 1, r, pn*2 + 1);
}
pushup(pn);
}
//查找
int srch(int L, int R, int l, int r, int pn){ //L, R表示操作区间,l, r表示当前区间,pn表示当前位置
if(L <= l && r <= R){
return sum[pn]; //在操作区间内的就返回
}
int m = (l + r) / 2;
pushdown(m - l + 1, r - m, pn); //下放
int ans = 0;
if(L <= m){
ans += srch(L, R, l, m, pn * 2);
}
if(R > m){
ans += srch(L, R, m + 1, r, pn*2 + 1);
}
return ans;
}
改成结构体数组之后,除了建树的时候要写上左右区间是为了确保建树完成,其他的操作就不需要左右区间了,直接使用节点位置编号pn就可以完成后续操作。
结构体数组的线段树代码:
#include <cstdio>
#include <iostream>
#define Max 10005
using namespace std;
struct tree{ //l, r表示左右区间端点,val表示这个区间的值,mark是标记
int l;
int r;
int val;
int mark = 0;
}sum[Max * 4];
int ar[Max]; //用来存原数组的下标
//上推
void pushup(int pn){
sum[pn].val = sum[pn * 2].val + sum[pn*2 + 1].val; //更新节点信息求和
}
//下推
void pushdown(int lsn, int rsn, int pn){ //lsn, rsn是左右子树的数字个数,pn是当前节点编号
if(sum[pn].mark){
sum[pn * 2].mark += sum[pn].mark;
sum[pn*2 + 1].mark += sum[pn].mark;
sum[pn * 2].val += sum[pn * 2].mark * lsn;
sum[pn*2 + 1].val += sum[pn*2 + 1].mark * rsn;
sum[pn].mark = 0;
}
}
//建树
void build(int l, int r, int pn){ //[l, r]表示当前操作区间,pn表示当前操作的节点编号
sum[pn].l = l;
sum[pn].r = r;
if(sum[pn].l == sum[pn].r){ //如果到达叶节点就储存数组值
sum[pn].val = ar[l];
return ;
}
int m = (l + r) / 2;
build(l, m, pn * 2); //左建树
build(m + 1, r, pn*2 + 1); //右建树
pushup(pn); //全都更新求和
}
//修改节点的值
void poi_update(int pos, int num, int pn){ //pos代表要修改的那个节点的位置,num是要加上的数,pn是当前节点编号
if(sum[pn].l == sum[pn].r){
sum[pn].val += num;
return ;
}
int m = (sum[pn].l + sum[pn].r) / 2;
if(pos <= m){
poi_update(pos, num, pn * 2);
}
else{
poi_update(pos, num, pn*2 + 1);
}
pushup(pn);
}
//区间的修改
void edge_update(int L, int R, int num, int pn){ //[L, R]表示操作区间,num是要改变的值,pn是当前位置
if(L <= sum[pn].l && sum[pn].r <= R){ //如果在操作区间内就改变,并且留下mark标记
sum[pn].val += num * (sum[pn].r - sum[pn].l + 1);
sum[pn].mark += num;
return ;
}
int m = (sum[pn].l + sum[pn].r) / 2;
//pushdown(m - sum[pn].l + 1, sum[pn].r - m, pn);
if(L <= m){
edge_update(L, R, num, pn * 2);
}
if(m < R){
edge_update(L, R, num, pn*2 + 1);
}
pushup(pn);
}
//查找
int srch(int L, int R, int pn){ //L, R表示操作区间,pn表示当前位置
if(L <= sum[pn].l && sum[pn].r <= R){
return sum[pn].val; //在操作区间内的就返回
}
int m = (sum[pn].l + sum[pn].r) / 2;
pushdown(m - sum[pn].l + 1, sum[pn].r - m, pn); //下放
int ans = 0;
if(L <= m){
ans += srch(L, R, pn * 2);
}
if(R > m){
ans += srch(L, R, pn*2 + 1);
}
return ans;
}