「学习笔记」线段树

「学习笔记」线段树

Question

用一个数据结构维护一个数列,支持:

  • 单点/区间修改
  • 区间求和/最大值/最小值/...

DateStructure:线段树

整体思想

线段树就是一大段分成两小段,对于小段再继续分割,更新时更新父节点,查询时合并区间的查询。

操作方法

单点修改

从最大区间开始一直寻找包含修改点的区间,同时对包含此点区间进行值的修改。

区间查询

从最大区间开始一直遍历两个子区间,每个区间有三种情况:

  1. 与查询区间完全不相交,此时直接跳出。
  2. 被查询区间完全包含,此时返回此区间的值。
  3. 1,2都不满足,此时继续遍历两个子区间。

Lazy Tag

当进行区间修改的时候,如果一次性更新到最底层的节点时,时间就会变成 \(O(n)\) ,稳稳炸飞。

这时候我们就要打“懒标记”了。

当一个区间完全被包含在修改区间内的时候,就给他打上标记,当查询到它的子区间时再去下传标记。

Code

单点查询和更改更简单一些,会了区间肯定能实现出来,这里就只粘上区间更改和查询的板子了。

其实就是懒。

void pushdown(ll p){//下传标记
	if(!t[p].bj)return;
	t[p*2].bj+=t[p].bj,t[p*2].val+=t[p].bj*(t[p*2].r-t[p*2].l+1);
	t[p*2+1].bj+=t[p].bj,t[p*2+1].val+=t[p].bj*(t[p*2+1].r-t[p*2+1].l+1);
	t[p].bj=0;
	//先打标记,再更新值
}ll build_tree(ll p,ll l,ll r){//
	t[p].l=l,t[p].r=r;
	if(l==r)scanf("%lld",&t[p].val);
	//最底层的点
	else{
		ll mid=(l+r)>>1;
		t[p].val=build_tree(p*2,l,mid)+build_tree(p*2+1,mid+1,r);
		//建儿子
	}return t[p].val;
}void update(ll p,ll l,ll r,ll d){//区间更新
	ll le=t[p].l,ri=t[p].r;
	if(le>r||ri<l)return;
	if(le>=l&&ri<=r)t[p].val+=d*(t[p].r-t[p].l+1),t[p].bj+=d;
	//打上标记
	else{
		pushdown(p),update(p*2,l,r,d),update(p*2+1,l,r,d);
		t[p].val=t[p*2].val+t[p*2+1].val;
		//给儿子下传之前的标记
	}
}ll query(ll p,ll l,ll r){//查询
	ll le=t[p].l,ri=t[p].r,ans;
	if(le>r||ri<l)return 0;
	if(le>=l&&ri<=r)return t[p].val;
	pushdown(p);//下传标记
	return ans=query(p*2,l,r)+query(p*2+1,l,r);
}

Example:山海经

因为教练给我们留的六道题中五道都是裸板子,所以只能直接放毒瘤题了。

Meaning of the Problem

给你一个序列,每次查询区间 \([x,y]\) 的最大子段和。

Solution

我们要维护一堆东西:

  • 区间和
  • 最大前缀和其右端点
  • 最大子段和其左右端点
  • 最大后缀和其左端点

我们要通过合并来维护子段和。

  1. 对于一个区间的最大前缀和有两种情况:
    1. 左子区间的最大前缀和。
    2. 左子区间和加右子区间的最大前缀和。
  2. 对于一个区间的最大后缀和有两种情况:
    1. 右子区间的最大后缀和。
    2. 右子区间和加左子区间的最大后缀和。
  3. 对于一个区间的最大子段和有三种情况:
    1. 左子区间的最大子段和。
    2. 右子区间的最大子段和。
    3. 左子区间的最大后缀和加右子区间的最大前缀和。

只要我们把板子里的"求和操作"改成"合并操作"就可以了。

这道题很恶心(对于线段树初学者来说),很适合锻炼代码能力,我写+调了一个早上才过,同机房的大佬们只有两个调到了晚上才过,大多数人还在调……

Code

#include <bits/stdc++.h>
#define _for(i,a,b) for(int i=a;i<=b;++i)
#define for_(i,a,b) for(int i=a;i>=b;--i)
#define ll long long
using namespace std;
const int N=1e5+10,inf=0x3f3f3f3f;
ll n,m,x,y,a[N],ans;
struct stru{
	ll val,l,r;//区间与值
	ll qz,qr,hz,hl;//维护前后缀
	ll zz,zl,zr;//维护中缀
	#define ls p<<1
	#define rs p<<1|1
}t[4*N];
inline ll read(){
	int x=0,w=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-48,c=getchar();
	return w*x;
}stru merge(stru a,stru b){//合并
	stru p;//合并结果区间
	//特判
	if(a.l==0&&a.r==0&&b.l==0&&b.r==0)return stru{0,0,0,0,0,0,0,0,0,0};
	if(a.l==0&&a.r==0)return b;
	if(b.l==0&&b.r==0)return a;
	//左右端点
	p.l=a.l,p.r=b.r;
	//区间和
	p.val=a.val+b.val;
	//前缀
	if(a.val+b.qz>a.qz)p.qz=a.val+b.qz,p.qr=b.qr;
	else p.qz=a.qz,p.qr=a.qr;
	//后缀
	if(b.val+a.hz<b.hz) p.hz=b.hz,p.hl=b.hl;
	else p.hz=b.val+a.hz,p.hl=a.hl;
	//中缀
	int qjh=a.hz+b.qz;//前加后
	if(a.zz>=qjh&&a.zz>=b.zz)p.zz=a.zz,p.zl=a.zl,p.zr=a.zr;
	else if(qjh>=b.zz)p.zz=qjh,p.zl=a.hl,p.zr=b.qr;
	else p.zz=b.zz,p.zl=b.zl,p.zr=b.zr;
	return p;
}void build_tree(int p,int l,int r){
	#define f t[p]
	f.l=l,f.r=r;
	if(l==r){
		f.zz=f.val=read();
		f.qz=f.hz=f.val;
		f.qr=f.zl=f.zr=f.hl=l;
	}else{
		int mid=(l+r)>>1;
		build_tree(ls,l,mid),build_tree(rs,mid+1,r);
		f=merge(t[ls],t[rs]);
	}
}stru query(int p,int l,int r){
	int le=t[p].l,ri=t[p].r;
	stru tmp=stru{0,0,0,0,0,0,0,0,0,0};
	if(le>r||ri<l)return tmp;
	if(le>=l&&ri<=r)tmp=t[p];
	else tmp=merge(query(p*2,l,r),query(p*2+1,l,r));
	return tmp;
}
int main(){
	freopen("date.in","r",stdin);
	n=read(),m=read();
	build_tree(1,1,n);
	while(m--){
		x=read(),y=read();stru ans=query(1,x,y);
		printf("%lld %lld %lld\n",ans.zl,ans.zr,ans.zz);
	}
	return 0;
}

\[\huge\mathfrak{The\ End} \]

posted @ 2022-01-18 22:06  K8He  阅读(160)  评论(0编辑  收藏  举报