线段树
- 线段树的每个结点都代表一个区间。
- 线段树有唯一的根节点代表整个范围,比如:[1,N];
- 线段树的每个叶子结点都代表一个长度为1的元区间 [x,x];
- 对于每个内部节点[l,r],它的左节点是[l,m],右节点是[m+1,r],其中m=(l+r)/2(向下取整)。
线段树的基本用途是对序列进行维护,支持查询和修改的指令
我采用了结构体的方式存储线段树的信息:
struct SegmentTree
{
int l, r;
int data;
} t[N * 4 + 5];
int a[N]; //原始数据数组
建树:
void build(int p,int l,int r) //存储区间和的线段树
{
t[p].l = l, t[p].r = r; //节点p代表[l,r]
if(l==r) //单点
{
t[p].data = a[l];
return;
}
int m = (l + r) / 2; //折半
int ls = 2 * p, rs = ls + 1;
build(ls, l, m); //建左子树
build(rs, m + 1, r); //建右子树
t[p].data = t[ls].data + t[rs].data; //p代表的区间和 = ls代表的区间和 + rs代表的区间和
}
void build(int p,int l,int r) //存储区间最大值的线段树
{
t[p].l = l, t[p].r = r;
if(l==r)
{
t[p].date = a[l];
return;
}
int m = (l + r) / 2;
int ls = 2 * p, rs = ls + 1;
build(ls, l, m);
build(rs, m + 1, r);
t[p].date = max(t[ls].date,t[rs].date); //p区间的最大值 = max(ls最大值,rs最大值)
}
这里注意,ls和rs为p的两个子区间,故可以通过 t[p].date = max(t[ls].date,t[rs].date);得出p区间的最大值,但是不可以通过ls和p求出rs区间的最大值。
单点修改
void add(int p,int x,int v) //将x位置的数增加v 同样是存储区间和的线段树
{
if(t[p].l==t[p].r)
{
t[p].data += v;
return;
}
int m = (t[p].l + t[p].r) / 2;
int ls = 2 * p, rs = ls + 1;
if(x<=m)
add(ls, x, v);
else
add(rs, x, v);
t[p].data = t[ls].data + t[rs].data;
}
void add(int p,int x,int v) //维护区间最大值的线段树 将x位置的数值改为v
{
if(t[p].l==t[p].r)
{
t[p].date = v;
return;
}
int m = (t[p].l + t[p].r) / 2;
int ls = 2 * p, rs = ls + 1;
if(x<=m)
add(ls, x, v);
else
add(rs, x, v);
t[p].date = max(t[ls].date,t[rs].date);
}
询问
int Query(int p,int l,int r) //询问l-r区间和
{
if(l<=t[p].l&&r>=t[p].r) //l-r完全覆盖了p代表的区间
{
return t[p].data; //直接返回值
}
int m = (t[p].l + t[p].r) / 2; //向下取整
int ls = 2 * p, rs = ls + 1;
int sum = 0;
if(l<=m) //p此时的区间的左半边和l-r有交集但不完全被完全覆盖
{
sum += Query(ls, l, m); //查询左半边 此时的l-r其实是l-m
}
if(r>m) //这里没有等于
{
sum += Query(rs, m + 1, r); //同理
}
}
int Query(int p,int l,int r) //询问区间最大值
{
if(l<=t[p].l&&r>=t[p].r)
{
return t[p].date;
}
int m = (t[p].l + t[p].r) / 2;
int ls = 2 * p, rs = ls + 1;
int maxx=-inf; //初始化最大值为-inf
if(l<=m)
maxx=max(maxx,Query(ls,l,r));
if(r>m)
maxx=max(maxx,Query(rs,l,r));
return maxx;
}
区间修改,区间查询
这里以洛谷的模板题举例(也可以用树状数组做,明天补上
#include <cmath>
#include <queue>
#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define forn(i, n) for (int i = 0; i < (n); i++)
#define forab(i, a, b) for (int i = (a); i <= (b); i++)
#define forba(i, b, a) for (int i = (b); i >= (a); i--)
#define mset(a, n) memset(a, n, sizeof(a))
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define P pair<int,int>
#define fi first
#define se second
using namespace std;
#define N 100005
#define inf 0x3f3f3f3f
#define ll long long
ll ans, temp[N], x;
struct SegmentTree
{
int l, r;
ll data, Add;
} t[N * 4 + 5];
int n, m;
void build(int p,int l,int r)
{
t[p].l = l;
t[p].r = r;
t[p].Add = 0;
if(l==r)
t[p].data = temp[l];
else
{
int m = (l + r) / 2;
int ls = 2 * p, rs = 2 * p + 1;
build(ls, l, m);
build(rs, m + 1, r);
t[p].data = t[ls].data + t[rs].data;
}
}
void add(int p,int l,int r,ll d)
{
if(t[p].l==l&&t[p].r==r)
{
t[p].Add += d;
}
else
{
int ls = 2 * p, rs = 2 * p + 1;
int m = t[ls].r;
t[p].data += (r - l + 1) * d;
if(l>m)
add(rs, l, r, d);
else if(r<=m)
add(ls, l, r, d);
else
{
add(ls, l, m, d);
add(rs, m + 1, r, d);
}
}
}
void Query(int p,int l,int r,ll d)
{
d += t[p].Add;
if(t[p].l==l&&t[p].r==r)
{
ans += (t[p].data + d * (r - l + 1));
}
else
{
int ls = 2 * p, rs = 2 * p + 1;
int m = t[ls].r;
if(l>m)
Query(rs, l, r, d);
else if(r<=m)
Query(ls, l, r, d);
else
{
Query(ls, l, m, d);
Query(rs, m + 1, r, d);
}
}
}
int main()
{
scanf("%d%d", &n, &m);
forab(i, 1, n) scanf("%lld", temp + i);
build(1, 1, n);
forab(i,1,m)
{
int x;
scanf("%d", &x);
if(x==1)
{
ll a, b, c;
scanf("%lld %lld %lld", &a, &b, &c);
add(1, a, b, c);
}
else
{
ll a, b;
scanf("%lld %lld", &a, &b);
ans = 0;
Query(1, a, b, 0);
printf("%lld\n", ans);
}
}
system("pause");
}
/*
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4*/
在增加时,如果要加的区间正好覆盖一个节点,则增加其节点的Add值,不再往下走,否则要更新data,再将增量往下传在查询时,如果待查区间不是正好覆盖一个节点,就将节点的Add往下带,然后将Add代表的所有增量累加到data上后将Inc清0,接下来再往下查询
未完。。。