一、单点修改,区间查询
(一)查询某区间内最大值:acwing最大数
如果是静态问题,可以用RMQ(倍增)来写。
单点修改可以不用懒标记,尽量不用,麻烦。
线段树里的每一个点都是一个结构体,具体存什么根据题目而定:
- 问的是什么就存什么,比如【区间查询】问的就是某个区间的某种属性,就要存区间的左右端点位置和这个属性;
- 辅助信息,看一下当前属性能不能由两个子区间的属性算出来,如果不能就需要辅助信息
递归建树,比如节点i的两个子区间分别是2i和2i+1,只有叶子节点被真实赋值:
- 从i=1开始建树
- 如果l=r,也就是叶子节点,就赋值,否则递归
- 维护,一般需要,偶尔建的树为空就不需要
维护有两种,用子区间维护父亲区间(从下往上维护),用父亲区间维护子区间(从上往下维护):
- 从下往上,pushup。找的时候从上往下找,找到底(叶子节点)并修改,修改完回溯,回溯的时候更新父节点的信息
- 从上往下,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你能回答这些问题吗
结构体里如果只存左右端点和当前区间的最大连续子段和:当前区间的属性不能由左右两个子区间的属性得到。所以考虑如何用左右两个子区间的属性得到当前区间的属性:
- 左右区间的属性:lmax和rmax
- 跨两个区间的属性,左区间的后缀、右区间的前缀:l和r
别忘了考虑新加的属性能不能直接求:
- 左右区间的最大连续子段和:可以直接根据左右子区间得到
- 包含左区间最后一个数的最大连续子段和、包含右区间第一个数的最大连续子段和。
例如,对于当前区间,它的最大前缀和:它的左子区间的最大前缀和、它的左子区间和和它的右子区间的最大前缀和。它的最大后缀和同理。
所以还有加上一个属性:区间和。
所以要存:
- 区间左右端点位置:l和r
- 区间内的最大连续子段和:max
- 区间的最大连续前缀和、区间的最大连续后缀和:lx和rx
- 区间和: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=