线段树的应用范围非常广,可以处理很多与区间有关的题目。
将区间抽象成一个节点,在这个节点中储存这个区间的一些值,那么如果看成节点的话,这就很像一棵满二叉树,所以我们可以用一维数组来储存节点。那么就要考虑父子节点之间的关系。
如果一个一个节点的下标是x,
那么父节点就是x/2
左子节点就是2x(可以写为x<<1),右子节点就是2x+1(可以写为x<<1+1)
那么我们就可以实现节点之间的转移操作,实际上相互影响的也只有父子节点之间相互影响,所以在更新的时候有两种更新方式,一种是用子节点来更新父节点(pushup),一种是用父节点来更新子节点(pushdown)。
还有一个问题就是这个一维数组该开多大,假设区间大小为n,那么我们就将数组开到4n大小。
既然用线段树,那么肯定要定义结构体来表示每个点,结构体的定义首先要有l,r来表示区间范围,然后需要维护的值肯定要放入结构体中,至于其他的变量就要根据维护值是否能用子节点算出父节点来决定,如果不能的话,那么就要考虑引入新的变量。
线段树看起来似乎很高深,但是实际上只有几个函数,模板比较固定:
build()//初始化线段树
modify()//修改
pushup()//用子节点更新父节点
pushdown()//将父节点的更新传到子节点
query() //查询操作
对于这些操作,我们还是结合具体的例题来分析。
1275. 最大数(活动 - AcWing)
题目稍微翻译一下就是,向一个空数组中插入数,在插入的同时进行局部区间最大值的访问。用上线段树后思路也没什么复杂的,我们可以先将整个数组先用线段树维护起来,空位置对应的区间就是0,那么我向末尾插入数的时候,实际上也就相当于对区间进行单点修改,单点修改的话实际上容易想到树状数组,但是树状数组用到了前缀和的原理,前缀和是没办法解决最大值问题的,所以这里还是要用线段树来实现。
我们先来确定结构体如何确定,首先得有l,r来表示区间,然后max也必须定义在结构体中,然后判断是否能用子节点的值来更新父节点,显然是可以的,父区间的最大值=max(左子区间的最大值,右子区间的最大值)。
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
struct node
{
int l,r,mx;
}tr[4*N];
int m,p;
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);
}
void pushup(int u)
{
tr[u].mx=max(tr[u<<1].mx,tr[u<<1|1].mx);
}
void modify(int u,int x,int c)
{
if(tr[u].l==x&&tr[u].r==x) tr[u].mx=c;
else
{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,c);
else modify(u<<1|1,x,c);
pushup(u);
}
}
int query(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].mx;
int mid=tr[u].l+tr[u].r>>1;
if(l>mid) return query(u<<1|1,l,r);
else if(r<=mid) return query(u<<1,l,r);
else return max(query(u<<1,l,r),query(u<<1|1,l,r));
}
int main()
{
int last=0,n=0;
scanf("%d%d",&m,&p);
build(1,1,m);
while(m--)
{
char op[2];
int a;
scanf("%s%d",op,&a);
if(op[0]=='A')
{
a=((long long)last+a)%p;
modify(1,++n,a);
}
else
{
last=query(1,n-a+1,n);
cout<<last<<endl;
}
}
}
ps:修改单点的话就不用pushdown操作了,pushdown需要用到懒标记,有些麻烦,能不用当然更好。
245. 你能回答这些问题吗(245. 你能回答这些问题吗 - AcWing题库)
这里是单点修改,很容易想到用树状数组来实现,但是树状数组只能维护类似于前缀和这种一整个区间的东西,而我们查询区间中的连续最大子段和,显然用树状数组就没办法实现,那么如果用线段树的话又该怎么实现呢?还是先来看如何定义结构体,显然我们需要储存最大连续子段和,但是这样够不够呢,显然是不够的,因为子节点无法更新父节点,虽然父区间的最大值有可能是左右子区间中的一段,但是还有可能是跨区间的,如果是跨区间的话,要想更新就需要用到左区间的最大后缀和右区间的最大前缀,那么现在就要多维护两个变量——最长前缀和最长后缀,那么现在考虑最长前缀和最长后缀能否能通过子节点来更新父节点,显然是不可以,以最长前缀为例,要用子节点计算的话,有两种情况,一种就是左子节点的最长前缀,一种是左区间加右区间的最大前缀。所以我们实际上还是要维持一个区间和,那么区间和可以由子区间更新父区间吗?当然可以。至此便不用再加别的变量就可以实现这个问题了。