线段树
- 基本概念
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。
来自百度百科
- 实现原理
每个节点代表一个区间,在对线段操作的线段树中,每个点代表一条线段,在用线段树维护数列信息的时候,每个点代表一个数,但本质上都是每个点代表一个数。
从这个很丑的图可以看出:维护的还是最小的儿子节点
1、每个节点的左孩子区间范围为[l,mid],右孩子为[mid+1,r]
2、对于结点k,左孩子结点为2k,右孩子为2k+1,这符合完全二叉树的性质
模板代码
从PPT上复制总结下来的
- 定义线段树
struct Node{
int left, right;
…
}node[MAXN*4];
//一般开到四倍 即MAXN<<=2;
- 建树 Build
即初始化树
#define L(u) (u<<1)//左孩子 即u*2
#define R(u) (u<<1|1)//右孩子 即u*2+1
void Build (int u,int left,int right){ //u表当前结点
//left right 表左区间范围[left,right]
node[u].l = left,node[u].r = right;
......... //结点信息的初始化
if (node[u].l == node[u].r){//到叶结点 return
.........//某些赋值
return ;
}
int mid = (node[u].l + node[u].r)>>1;
Build (L(u),left,mid);
Build (R(u),mid+1,right);
Pushup(u);
}
- 向上回溯 Pushup
该函数就是由子结点递归回来 修改父结点中的信息
void Pushup(int u){
node[u].sum = node[L(u)].sum + node[R(u)].sum;//价值
…….
…… return ;
}
- 延时更新 Pushdown
调用此函数时,结点中一般有个状态标志位 用来判断是否需要往下更新,可以防TLE 只在查询的时候修改
void Pushdown (int u) {//延迟覆盖操作
if (node[u].state == -1) {
//更新父结点,左右子结点信息 }
else if (node[u].state == 1){
//更新父结点,左右子结点信息
}
else {
………
}
}
- 更新修改操作 Update
点的修改
void Update(int L,int C,int l,int r,int rt){//l,r表示当前节点区间,rt表示当前节点编号
if(l==r){//到叶节点,修改
Sum[rt]+=C;
return;
}
int m=(l+r)>>1;
//根据条件判断往左子树调用还是往右
if(L <= m) Update(L,C,l,m,rt<<1);
else Update(L,C,m+1,r,rt<<1|1);
PushUp(rt);//子节点更新了,所以本节点也需要更新信息
}
区间的修改
void Update(int L,int R,int C,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
if(L <= l && r <= R){//如果本区间完全在操作区间[L,R]以内
Sum[rt]+=C*(r-l+1);//更新数字和,向上保持正确
Add[rt]+=C;//增加Add标记,表示本区间的Sum正确,子区间的Sum仍需要根据Add的值来调整
return ;
}
int m=(l+r)>>1;
PushDown(rt,m-l+1,r-m);//下推标记
//这里判断左右子树跟[L,R]有无交集,有交集才递归
if(L <= m) Update(L,R,C,l,m,rt<<1);
if(R > m) Update(L,R,C,m+1,r,rt<<1|1);
PushUp(rt);//更新本节点信息
}
以上都以求和为例
- 查询操作 Query
Data Query(int u,int left,int right)
{
if (left <= node[u].l&&node[u].r <= right)
return node[u].sum;
Pushdown(u);//延迟更新
int mid = (node[u].l + node[u].r)>>1;
if (right <= mid) return Query(L(u),left,right);
else if (left > mid) return Query(R(u),left,right);
else return (Query(L(u),left,mid) + Query(R(u),mid+1,right));
Pushup(u);
}
练习
1.EXAM 1711 http://exam.upc.edu.cn/problem.php?id=1711
维护的应该是区间的最小值,很容易T,需要输入挂,延迟更新应该没有用到,然后其实这道题正解不是线段树,但是可以用用线段树的思想:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+7;
int minn[maxn<<2],add[maxn<<2],s[maxn];
#define l(u) (u<<1)
#define r(u) (u<<1|1)
inline bool scan_d(int &num)
{
char in;
bool IsN=false;
in=getchar();
if(in==EOF) return false;
while(in!='-'&&(in<'0'||in>'9')) in=getchar();
if(in=='-')
{
IsN=true;
num=0;
}
else num=in-'0';
while(in=getchar(),in>='0'&&in<='9')
{
num*=10,num+=in-'0';
}
if(IsN) num=-num;
return true;
}
struct node
{
int l,r;
} t[maxn<<2];
void pushup(int u)//向上更新区间最小值
{
minn[u]=min(minn[l(u)],minn[r(u)]);
}
void pushdown(int u)
{
minn[u]-=add[u];
add[l(u)]+=add[u];
add[r(u)]+=add[u];
add[u]=0;
}
void build(int u,int l,int r)
{
t[u].l=l,t[u].r=r;
if(l==r)
{
minn[u]=s[l];
return;
}
if(l>r) return;
int mid=(l+r)>>1;
build(l(u),l,mid);
build(r(u),mid+1,r);
pushup(u);
}
void update(int u,int al,int ar,int x)
{
if(add[u])pushdown(u);//如果u区间还没有处理
if(al<=t[u].l&&t[u].r<=ar)
{
add[u]=x;
pushdown(u);
return;
}
if(al>t[u].r||ar<t[u].l)return;
update(l(u),al,ar,x);
update(r(u),al,ar,x);
pushup(u);
}
int main()
{
int flag=0;
int n,m;
scan_d(n);
scan_d(m);
for(int i=1; i<=n; i++) scan_d(s[i]);
build(1,1,n);
int d,x,y;
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&d,&x,&y);
update(1,x,y,d);
if(minn[1]<0&&!flag)
{
printf("-1\n");
flag=i;
}
}
printf("%d\n",flag);
return 0;
}