线段树是什么东西?
简单的说,线段树是把 [1,n] 一段区间不断二分二分,直至每个树的节点是[i,i]的区间
[1,7]
[1,4] [5,7]
[1,2] [3,4] [5,6] [7,7]
[1,1] [2,2] [3,3] [4,4] [5,5] [6,6]
(线段树未必是一个满二叉树,但一定是个平衡二叉树)
/* 讨论在数组的情况下构造*/
Section A 基本操作
一、如何构造?
a) 如何构造左右子树关系?
假设根节点为root,左儿子lson 右儿子rson
不妨lson = root << 1 , rson = lson | 1
画一张图,把节点序号的二进制写出来你就知道为什么这么做
b) 如何建立树?
从最大的区间开始,每次二分 mid = (lson+rson) >> 1
递归建立lson,mid区间和mid+1,rson区间
当区间只有一个数的时候,保存数据
c) 更新:
在构造过程中首先是build(lson,mid),build(mid+1,rson)
然后需要发挥线段树的功能,能够快速询问维护一段区间的值比如
tree[root].max = max{tree[lson].max,tree[rson],max}
tree[root].sum = tree[lson].sum + tree[rson].sum
二、询问操作
a) 朴素法
在构造时,对于每个节点保存左右区间的范围。
然后递归寻找Query(root,L,R)
分三类情况:
完全在左区间 Query(lson,L,R)
完全在右区间 Query(rson,L,R)
区间跨越范围 Query(lson,L,Mid),Query(rson,Mid+1,r)
b) 全局法:
在构造时:不需要考虑每个节点的范围
根据Query函数调用时的区间和查询所用的区间直接判断。
设L,R是当前调用询问的区间;qL,qR是查询区间
还是分二类情况:若[L,R] 包含于 [qL,qR] 直接考虑当前root的值
否则考虑qR,qL与Mid的关系
若qL <= Mid 考虑Query(lson,qL,Mid)
若Mid < qR 则考虑Query(rson,Mid+1,qR)
至于最大最小还是求和怎么操作,应该心里有数
事实上,给定了线段长度,每个节点本身就对应了一段区间,朴素法其实有些多余,不建议使用。
三、单点修改 /* 以下均用全局法*/
a) 单点修改
其实和单点查询的思路一摸一样,找到后顺便更新就是。
b)单点覆盖
把a中的 += 变成 =
/* 有人可能会问为什么这里没有build函数,那是因为所谓build,也就是在每个节点上add一次罢了 */
#include
using namespace std;
typedef long long long_int;
const int INF = 1000000000;
const int maxn = 500100;
const int maxnode = maxn << 2;
bool isSet = true;
int op, qL, qR, pos, val; //pos表示修改的位置 val表示修改量
long_int _sum , _min , _max;
struct IntervalTree {
long_int minv[maxnode] , maxv[maxnode] , sumv[maxnode];
inline void init(int x , long_int val){
minv[x] = maxv[x] = sumv[x] = val; //把某个单节点区域初始化,最大最小和都是这个值
}
inline void add(int x , long_int val){
/* 在下标为x的树节点上 所有维护的值都加上val 即单点增加*/
minv[x] += val;
maxv[x] += val;
sumv[x] += val;
}
inline void pushup(int o){
/* 更新下标为 o 的树节点*/
int lson = o << 1;
int rson = lson | 1;
minv[o] = min(minv[lson],minv[rson]);
sumv[o] = sumv[lson] + sumv[rson];
maxv[o] = max(maxv[lson],maxv[rson]);
}
void update(int o, int L, int R) {
int M = (L + R) >> 1;
if(L == R) {
if(isSet) init(o,val);
else add(o,val);
}
else {
if(pos <= M) update(o*2, L, M);
else update(o*2+1, M+1, R);
pushup(o);
}
}
void query(int o, int L, int R) {
int M = (L+R) >> 1;
if(qL <= L && R <= qR)
{
_min = min(_min,minv[o]);
_max = max(_max,maxv[o]);
_sum += sumv[o];
return;
}
if(qL <= M)
query(o*2, L, M);
if(M < qR)
query(o*2+1, M+1, R);
}
};
IntervalTree tree;
int main() {
int n, m;
cin >> n;
memset(&tree, 0, sizeof(tree));
long_int x = 0;
for (int i = 1 ; i <= n ; ++i)
{
cin >> x;
pos = i;
val = x;
tree.update(1,1,n);
}
cin >> m;
isSet = false;
while(m--) {
scanf("%d", &op);
if(op == 1) {
scanf("%d%d", &pos, &val);
tree.update(1, 1, n);
} else {
scanf("%d%d", &qL, &qR);
_min = INF , _max = -_min , _sum = 0;
tree.query(1, 1, n);
cout << _sum << endl;
}
}
return 0;
}
区间覆盖:
1、如何维护?
首先想到多维护一个数组setv[],用来记录是否被某个数覆盖。
然后我们就可以像区间查询一样,在O(lgn)时间里准确地给某段区域打上标记了。
若每次的区间都正好一样是没问题,但万一出现交叉染色的情况怎么办?下传标记!
什么意思?如果你打算给区间S染色,然而这段区间的一部分子区间Q已经染过色了,那么你把S区间的两个子区间给传递上这个染色标记,然后把S区间的标记设置为无标记。这样一来在递归的过程中可以确保该有的准确标记不丢失。
2、如何查询?
首先基础框架还是和上面一样。只需要做点当标记存在时的特殊处理:
如果查询的是最大最小值,那么只要比较当前的极值和标记上的值。
#include
#define sz (R-L+1)
#define NoPos -21000000
using namespace std;
typedef long long long_int;
const int INF = 1000000000;
const int maxn = 500100;
const int maxnode = maxn << 2;
int op, qL, qR, p, v;
long_int _sum , _min , _max;
struct IntervalTree {
long_int minv[maxnode] , maxv[maxnode] , sumv[maxnode] , setv[maxnode];
void maintain(int o ,int L , int R)
{
int lson = o << 1 , rson = lson | 1;
if(R > L){
sumv[o] = sumv[lson] + sumv[rson];
minv[o] = min(minv[lson],minv[rson]);
maxv[o] = max(maxv[lson],maxv[rson]);
}
if(setv[o] != NoPos)
{
minv[o] = maxv[o] = setv[o];
sumv[o] = setv[o] * sz;
}
}
inline void pushdown(int o){
int lson = o << 1;
int rson = lson | 1;
if(setv[o] != NoPos){
setv[lson] = setv[rson] = setv[o];
setv[o] = NoPos;
}
}
void update(int o, int L, int R) {
int M = (L+R) >> 1;
int lson = o << 1 , rson = lson | 1;
if(qL <= L && qR >= R)
setv[o] = v;
else
{
pushdown(o);
if(qL <= M)
update(lson,L,M);
else
maintain(lson,L,M);
if(M < qR)
update(rson,M+1,R);
else
maintain(rson,M+1,R);
}
maintain(o,L,R);
}
void query(int o, int L, int R) {
int M = (L+R) >> 1;
if(setv[o] != NoPos){
_sum += setv[o] * (min(R,qR) - max(L,qL) + 1);
_max = max(_max,setv[o]);
_min = min(_min,setv[o]);
} else if (qL <= L && qR >= R){
_sum += sumv[o];
_min = min(_min,minv[o]);
_max = max(_max,maxv[o]);
} else {
int lson = o << 1 , rson = lson | 1;
if(qL <= M)
query(lson,L,M);
if(M < qR)
query(rson,M+1,R);
}
}
};
IntervalTree tree;
int main() {
int n, m;
cin >> n;
memset(&tree, 0, sizeof(tree));
long_int x = 0;
for (int i = 1 ; i <= n ; ++i)
{
cin >> x;
qL = qR = i;
v = x;
tree.update(1,1,n);
}
cin >> m;
while(m--) {
scanf("%d", &op);
if(op == 1) {
scanf("%d%d%d", &qL, &qR,&v);
tree.update(1, 1, n);
} else {
scanf("%d%d", &qL, &qR);
_min = INF , _max = -_min , _sum = 0;
tree.query(1, 1, n);
cout << _sum << endl;
}
}
return 0;
}
这样一看区间加法是否也简单了呢?自行思考我要打卡去了再见。