线段树详解
Posted stungyep
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树详解相关的知识,希望对你有一定的参考价值。
线段树及其应用
线段树的几个基础操作:建树,单点查询,单点修改,区间查询,区间修改。其代码的主要思想为二分。参考博客:https://blog.csdn.net/qq_39826163/article/details/81436440
数据结构:
struct node
{
int l; //左端点
int r; //右端点
int sum; //区间和,因题目而异
int f; //懒标记
}tree[4*maxn+1];
1.建树
建树的过程分为三步:1:给定左右端点的确定范围;2:如果是叶子结点,储存需要维护的信息;3:状态合并。下面是实现代码:
void build(int l,int r,int cur)
{
tree[cur].l = l,tree[cur].r = r;
if(tree[cur].l == tree[cur].r){
tree[cur].sum = arr[l];
return;
}
int mid = (l + r) >> 1;
build(l, mid, cur << 1);
build(mid + 1, r, cur << 1 | 1);
pushup(cur); //状态合并
}
2.单点查询
单点查询与二分查询法基本一致,如果当前枚举的点左右端点相等,即叶子节点,就是目标节点。如果不是,因为这是二分法,所以设查询位置为x,当前结点区间范围为了l,r,中点为mid,则如果x<=mid,则递归它的左孩子,否则递归它的右孩子。下面是实现代码:
int ask(int pos,int cur) //cur为当前结点,x为待查位置
{
if(tree[cur].l==tree[cur].r) return tree[k].value;
int mid=(tree[cur].l+tree[cur].r)>>1;
if(pos<=mid) ask(pos,cur<<1);
else ask(pos,cur<<1|1); //递归左右孩子
}
3.单点修改
和单点查询原理类似,结合建树过程,我们以增加某一个区间的长度代码为例,下面是实现代码:
void modify(int pos,int x,int cur) //在pos位置修改x(增加x),cur为当前结点编号
{
if(tree[cur].l == tree[cur].r){
tree[cur].sum += x;
return;
}
if(tree[cur].f) pushdown(cur);
int mid = (tree[cur].l + tree[cur].r) >> 1;
if(pos <= mid) modify(pos,x,cur<<1);
else modify(pos,x,cur<<1|1);
pushup(cur);
}
4.区间查询
区间查询分为三种状态,1、当前结点区间的值全部为答案的一部分,;2、当前结点区间只有一部分是答案,3、当前结点区间包含了待查询的区间,根据x,y与mid的情况往下走。
即mid=(l+r)/2
y<=mid ,即 查询区间全在,当前区间的左子区间,往左孩子走;x>mid 即 查询区间全在,当前区间的右子区间,往右孩子走否则,两个子区间都走。
下面是实现代码:
void query(int l,int r,int cur) //l,r为待查询区间
{
if(l <= tree[cur].l && tree[cur].r <=r ){
ans += tree[cur].sum;
return;
}
if(tree[cur].f) pushdown(cur); //将更新信息传递给左右子树
int mid = (tree[cur].l + tree[cur].r) >> 1;
if(l <= mid) query(l,r,cur<<1);
if(mid < r) query(l,r,cur<<1|1);
}
5.区间修改
如果要修改一个区间的值,给一个区间内的每个数都加或减或修改时,如果我们只想查询某一个子区间的值,如修改[1,100]而只查询[1,2]的值,如果给所有区间都改得画,在树的深度很高的情况下会很浪费。所以这里引入了一个新状态——懒标记,其作用是存储到这个节点的修改信息,暂时不把修改信息传到子节点。就像家长扣零花钱,你用的时候才给你,不用不给你。下面是懒标记的具体实现过程:
懒标记下移:
void pushdown(int cur)
{
tree[cur<<1].f+=tree[cur].f;
tree[cur<<1|1].f+=tree[cur].f;
tree[cur<<1].sum+=tree[cur].f*(tree[cur<<1].r-tree[cur<<1].l+1);
tree[cur<<1|1].sum+=tree[cur].f*(tree[cur<<1|1].r-tree[cur<<1|1].l+1);
tree[cur].f=0;
}
还有上面提到的pushup函数,我认为它和oushdown函数一起,是线段树的核心,其他的不过是模板而已,而这个是线段树真正灵活多变的地方,对于任意给定一个题目,你要依据题意,题目需要维护什么,你就维护什么,比如上面一直再说的oushup函数和这里的Pushdown,在这起到的是维护一个区间和的作用。不同题目真正不一样的代码应该就是这两个了。
//以维护区间和为例:
inline void pushup(int cur)
{
tree[cur].sum=tree[cur<<1].sum+tree[cur<<1|1].sum;
}
区间修改代码:
void modify_interval(int l,int r,int x,int cur) //[a,b]为待修改的区间,x为区间修改的值
{
if(tree[cur].l>=l&&tree[cur].r<=r) //当前区间全部对要修改的区间有用
{
tree[cur].value+=(tree[cur].r-tree[cur].l+1)*x; //(r-1+1)区间点的总数
tree[cur].f+=x;
return;
}
if(tree[cur].f) pushdown(cur); //懒标记下移
int mid=(tree[cur].l+tree[cur].r)>>1;
if(l<=mid) modify_interval(l,r,x,cur<<1);
if(r>mid) modify_interval(l,r,x,cur<<1|1);
pushup(cur);
}
例题AC代码:poj-2528:题意,每次在[l,r]区间贴广告,最多能看见多少个广告牌?
思路:离散化+线段树区间染色
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn=1e5+50;
struct node
{
int l,r,num;
}tree[maxn<<2];
int n,T,cnt,tot,li[maxn],ri[maxn];
int point[maxn<<1];
int ans;
bool vis[maxn];
inline void pushdown(int cur)
{
tree[cur<<1].num=tree[cur].num;
tree[cur<<1|1].num=tree[cur].num;
tree[cur].num=0;
}
inline void build(int l,int r,int cur) //initialization
{
tree[cur].l=l,tree[cur].r=r;
if(l==r){
tree[cur].num=0;
return;
}
int mid=(l+r)>>1;
build(l,mid,cur<<1);
build(mid+1,r,cur<<1|1);
tree[cur].num=0;
}
inline void modify(int l,int r,int x,int cur)
{
if(tree[cur].l>=l&&tree[cur].r<=r){
tree[cur].num=x;
return;
}
if(tree[cur].num) pushdown(cur);
int mid=(tree[cur].l+tree[cur].r)>>1;
if(l<=mid) modify(l,r,x,cur<<1);
if(mid<r) modify(l,r,x,cur<<1|1);
}
inline void query(int l,int r,int cur)
{
if(tree[cur].num&&!vis[tree[cur].num]){
vis[tree[cur].num]=1;
ans++;
return;
}
if(l==r) return;
if(tree[cur].num) pushdown(cur);
int mid=(l+r)>>1;
query(l,mid,cur<<1);
query(mid+1,r,cur<<1|1);
}
int main()
{
scanf("%d",&T);
while(T--)
{
memset(vis,false,sizeof(vis));
scanf("%d",&n);
cnt=ans=0;
for(int i=1;i<=n;++i) scanf("%d %d",&li[i],&ri[i]);
for(int i=1;i<=n;++i){
point[++cnt]=li[i], point[++cnt]=ri[i];
}
sort(point+1,point+cnt+1);
int now=unique(point+1,point+cnt+1)-(point+1);
tot=now;
for(int i=2;i<=now;++i){
if(point[i]-point[i-1]>1)
point[++tot]=point[i-1]+1;
}
sort(point+1,point+tot+1);
build(1,tot,1);
for(int i=1;i<=n;++i){
int l=lower_bound(point+1,point+tot+1,li[i])-point;
int r=lower_bound(point+1,point+tot+1,ri[i])-point; //O(n*logn) algorithm
modify(l,r,i,1);
}
query(1,tot,1);
printf("%d
",ans);
}
system("pause");
}
POJ-3468 题意:裸线段树区间修改+区间查询,注意区间查询的时候别忘了pushdown就好了。
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
const int maxn=1e5+50;
typedef long long LL;
int arr[maxn],n,q;
LL ans;
struct node
{
int l,r,f;
LL sum;
}tree[maxn<<2];
inline void pushup(int cur)
{
tree[cur].sum=tree[cur<<1].sum+tree[cur<<1|1].sum;
}
inline void build(int l,int r,int cur)
{
tree[cur].l=l,tree[cur].r=r;
tree[cur].sum=tree[cur].f=0;
if(tree[cur].l==tree[cur].r){
tree[cur].sum=1LL*arr[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,cur<<1);
build(mid+1,r,cur<<1|1);
pushup(cur);
}
inline void pushdown(int cur)
{
tree[cur<<1].f+=tree[cur].f;
tree[cur<<1|1].f+=tree[cur].f;
tree[cur<<1].sum+=1LL*tree[cur].f*(tree[cur<<1].r-tree[cur<<1].l+1);
tree[cur<<1|1].sum+=1LL*tree[cur].f*(tree[cur<<1|1].r-tree[cur<<1|1].l+1);
tree[cur].f=0;
}
inline void query(int l,int r,int cur)
{
if(l<=tree[cur].l&&tree[cur].r<=r){
ans+=tree[cur].sum;
return;
}
if(tree[cur].f) pushdown(cur);
int mid=(tree[cur].l+tree[cur].r)>>1;
if(mid>=l) query(l,r,cur<<1);
if(mid<r) query(l,r,cur<<1|1);
}
inline void modify(int l,int r,int x,int cur)
{
if(l<=tree[cur].l&&tree[cur].r<=r){
tree[cur].f+=x;
tree[cur].sum+=1LL*x*(tree[cur].r-tree[cur].l+1);
return;
}
if(tree[cur].f) pushdown(cur);
int mid=(tree[cur].l+tree[cur].r)>>1;
if(mid>=l) modify(l,r,x,cur<<1);
if(mid<r) modify(l,r,x,cur<<1|1);
pushup(cur);
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i) scanf("%d",&arr[i]);
build(1,n,1);
while(q--)
{
char t;
getchar();
scanf("%c",&t);
if(t=='Q'){
ans=0;
int a,b;
scanf("%d %d",&a,&b);
query(a,b,1);
printf("%lld
",ans);
}
else{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
modify(a,b,c,1);
}
}
system("pause");
}
ps:pushdown可以写在函数最上方,玄学写法我也不懂,先pushdown,wa了可以试试qwq
以上是关于线段树详解的主要内容,如果未能解决你的问题,请参考以下文章