线段树详解
线段树是一个二叉搜索树,和二叉树的顺序存储很像,不过我们的节点都表示的一个区间,具体维护什么信息因题目而定,查询信息是一个不断二分的过程,看似会有很多分叉,但一般另一个分叉是固定的数值,所以总体来说还是log(n) 的时间复杂度,但是 常数会比树状数组大些。
- 一直困惑我的一个点就是 查询操作中:
if( tr[u].l >=l && tr[u].r <=r )
{
return tr[u].v;
} - 我就困惑如果范围是2–5,现在的tr[u]里是2–3,这样就直接返回不就错了吗?事实上不是这样的,因为我们是从根节点开始遍历,所以第一次的范围肯定要比要求的范围要大,或者我们问的就是全部范围直接返回,否者都会继续细分下去,最后肯定是最开始的一层里面的return v返回最终的答案,所以说一开始担心的返回错是不需要担心的,那里的返回只是那一层最后一次递归返回结果而已,一步步回溯回来答案就更新为最终正确的答案了。
- 困惑的地方还有modify里面为什么pushup要写else里面,想了一下是因为如果写外面的话,叶子节点改变后就会直接执行pushup然后刚改的叶子节点又会被变为它的两个儿子的最大值,因为它没儿子所以可能数组开小的话会报错,没开小的话就会变为0,然后在网上回溯就都等于0了
int query(int u, int l, int r)
{
if (tr[u].l >= l && tr[u].r <= r) return tr[u].v; // 树中节点,已经被完全包含在[l, r]中了
int mid = tr[u].l + tr[u].r >> 1;
int v = 0;
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 idx,int x)
{
if(tr[u].l==idx&&tr[u].r==idx) tr[u].v=x;
else
{
int mid=tr[u].l+tr[u].r>>1;
if(idx<=mid) modify(u<<1,idx,x);
else modify(u<<1|1,idx,x);
pushup(u);
}
return;
}
维护区间最大值的例子
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct node{
int l,r;
int v;
}tr[N*4];
int m,p;
void pushup(int u)
{
tr[u].v=max(tr[u<<1].v,tr[u<<1|1].v);
return ;
}
void build(int u,int l,int r)
{
tr[u]={l,r};
if(l==r) return ;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
void modify(int u,int idx,int x)
{
if(tr[u].l==idx&&tr[u].r==idx) tr[u].v=x;
else
{
int mid=tr[u].l+tr[u].r>>1;
if(idx<=mid) modify(u<<1,idx,x);
else modify(u<<1|1,idx,x);
pushup(u);
//如果pushup(u)写外面就会在要插入的叶子节点pushup让刚插入的叶子节点再等于它两个儿子的最大值0了
}
return;
}
int query(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].v;
int v=0;
int mid=tr[u].l+tr[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;
}
int main()
{
scanf("%d %d",&m,&p);
build(1,1,m);
int n=0;
int a=0;
while(m--)
{
char op[2];
int x;
scanf("%s %d",op,&x);
if(*op=='Q')
{
a=query(1,n-x+1,n);
printf("%d\n",a);
}
else if(*op=='A')
{
modify(1,++n,((long long)a+x)%p);
}
}
return 0;
}