基础线段树

一、单点修改,区间查询

(一)查询某区间内最大值:acwing最大数

如果是静态问题,可以用RMQ(倍增)来写。
单点修改可以不用懒标记,尽量不用,麻烦。
线段树里的每一个点都是一个结构体,具体存什么根据题目而定:

  1. 问的是什么就存什么,比如【区间查询】问的就是某个区间的某种属性,就要存区间的左右端点位置和这个属性;
  2. 辅助信息,看一下当前属性能不能由两个子区间的属性算出来,如果不能就需要辅助信息

递归建树,比如节点i的两个子区间分别是2i和2i+1,只有叶子节点被真实赋值:

  1. 从i=1开始建树
  2. 如果l=r,也就是叶子节点,就赋值,否则递归
  3. 维护,一般需要,偶尔建的树为空就不需要

维护有两种,用子区间维护父亲区间(从下往上维护),用父亲区间维护子区间(从上往下维护):

  1. 从下往上,pushup。找的时候从上往下找,找到底(叶子节点)并修改,修改完回溯,回溯的时候更新父节点的信息
  2. 从上往下,pushdown。把父节点的修改,更新到儿子节点上。

单点修改:和建树一样用递归,从根节点1开始找要查询的叶子节点的位置,找到就修改,pushup更新父节点的信息。
区间最值查询:一样用递归从1开始找,找到多个在区间内的树枝,对它们的属性求max。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct Node {
   
	int l, r;
	int v;
} tr[200010 * 4];
ll n,m,p,last,t;
char op;
void build(int u, int l, int r) {
   
	tr[u].l=l,tr[u].r=r;
	if(l==r) return;
	int mid=l+r>>1;
	build(u<<1, l, mid);
	build(u<<1|1, mid+1, r);
}
int query(int u, int l, int r) {
   	//找所有完全包含的树枝 
	if(l<=tr[u].l&&tr[u].r<=r) return tr[u].v;
	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) {
   	//找n+1这个叶子节点 
	if(tr[u].l==tr[u].r&&tr[u].l==n) tr[u].v=(last+t)%p;
	else {
   
		int mid=(tr[u].l+tr[u].r)>>1;
		if(n<=mid) modify(u<<1);
		else modify(u<<1|1);
		tr[u].v = max(tr[u<<1].v, tr[u<<1|1].v);	//修改后从下往上维护 
	}
}
int main() {
   
	scanf("%lld%lld",&m,&p);
	build(1,1,m);
	for(int i=1; i<=m; i++) {
   
		getchar();
		scanf("%c%lld",&op,&t);
		if(op=='A') {
   
			n++;
			modify(1);	//修改n+1 
		} else {
   
			last = query(1, n-t+1, n);	//[n-t+1, n]内最值 
			printf("%lld\n",last);
		}
	}
	return 0;
}

(二)查询某区间内最大连续子段和:acwing你能回答这些问题吗

结构体里如果只存左右端点和当前区间的最大连续子段和:当前区间的属性不能由左右两个子区间的属性得到。所以考虑如何用左右两个子区间的属性得到当前区间的属性:

  1. 左右区间的属性:lmax和rmax
  2. 跨两个区间的属性,左区间的后缀、右区间的前缀:l和r

别忘了考虑新加的属性能不能直接求:

  1. 左右区间的最大连续子段和:可以直接根据左右子区间得到
  2. 包含左区间最后一个数的最大连续子段和、包含右区间第一个数的最大连续子段和。

例如,对于当前区间,它的最大前缀和:它的左子区间的最大前缀和、它的左子区间和和它的右子区间的最大前缀和。它的最大后缀和同理。

所以还有加上一个属性:区间和。
所以要存:

  1. 区间左右端点位置:l和r
  2. 区间内的最大连续子段和:max
  3. 区间的最大连续前缀和、区间的最大连续后缀和:lx和rx
  4. 区间和:s

修改和建树差不多,区别在于建树是修改所有叶子区间,而修改只需修改一个叶子区间
查询,有两种思路。
\quad 一是y总的四种情况,l<ul<ur<r直接回溯、l<ul<r<mid<r搜完左子树回溯、ul<mid<l<ur<r搜完右子树回溯、ul<l<r<ur两个子树都搜;
\quad 二其实也是分四种情况,l<ul<ur<r、ul<l<mid<ur<r、l<ul<mid<r<ur、ul<l<r<ur,只是最后一种情况在写的时候被第二、三种情况包含了,就变成了三种情况,l<ul<ur<r、l<mid<ur<r搜左子树、l<ul<mid<r搜右子树,左右子树都搜完,取了最大,再回溯。
显然,y总的版本更快。

#include<bits/stdc++.h>
using namespace std;
struct T {
   
	int l,r,s,lx,rx,max;
};
T t[2000010];
int n,m,a[500010];
void build(int p, int l, int r) {
   	//建树 
	t[p].l=l,t[p].r=r;
	if(l==r) {
   
		t[p].s=t[p].lx=t[p].rx=t[p].max=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	//pushup
	t[p].s=t[p<<1].s+t[p<<1|1].s;
	t[p].lx=max(t[p<<1].lx,t[p<<1].s+t[p<<1|1].lx);
	t[p].rx=max(t[p<<1|1].rx,t[p<<1|1].s+t[p<<1].rx);
	t[p].max=max(max(t[p<<1].max,t[p<<1|1].max),t[p<<1].rx+t[p<<1|1].lx);
}
T ask(int p, int l, int r) {
   	//查询 
	if(l<=t[p].l&&t[p].r<=r) return t[p];
	T a,b,ans;
	a.s=a.lx=a.rx=a.max=b.s=b.lx=b.rx=b.max=-0x3f3f3f3f;
	ans.s=0;
	int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid) {
   
		a=ask(p<<1,l,r);
		ans.s+=a.s;
	} 
	if(r>mid) {
   
		b=ask(p<<1|1,l,r);
		ans.s+=b.s;
}
	ans.max=max(max(a.max,b.max),a.rx+b.lx);
	ans.lx=max(a.lx,a.s+b.lx);
	ans.rx=max(b.rx,b.s+a.rx);
	if(l>mid) ans.lx=max(ans.lx,b.lx);
	if(r<=mid) ans.rx=max(ans.rx,a.rx);
	return ans;
}
void change(int p, int x, int w) {
   	//单点修改 
	if(t[p].l==t[p].r) {
   
		t[p].s=t[p].lx=t[p].rx=t[p].max=w;
		return ;
	}
	int mid=(t[p].l+t[p].r)>>1;
	if(x<=mid) change(p<<1,x,w);
	else change(p<<1|1,x,w);
	t[p].s=t[p<<1].s+t[p<<1|1].s;
	t[p].lx=max(t[p<<1].lx,t[p<<1].s+t[p<<1|1].lx);
	t[p].rx=max(t[p<<1|1].rx,t[p<<1|1].s+t[p<<1].rx);
	t[p].max=<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值