线段树除最后一层为满二叉树 所以用一个一维数组存整棵树
如果编号为x 父节点为x/2 左节点2*x 右节点2*x+1 下面从0开始
pushup:是已知子节点去算父节点的信息
比如总的sum就等于Lsum+Rsum
pushdown:将父节点的修改信息下传到子节点(懒标记 延迟标记)
基本操作
1.pushup(u) pushdown
2.build() 将一段区间初始化为线段树
3.modify() 修改
(1)单点修改
(2)修改区间( pushdown )
4.query()查询
线段树的倒数第一层节点大概有n个 前面的所有点大概2*n-1个 最后一层大概为前一层的两倍2*n
所以最坏的情况有4*n-1个点 所以开空间要开4*n
build操作
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);
pushup(u);//重点
}
query操作 O(logn)
[L,R]表示要查询的区间 [tl,tr]表示树中节点的范围是多少
1.[L,R]包含[tl,tr] 直接return
2.[L,R]和[tl,tr]有交集
左儿子有交集就递归左儿子 右儿子有交集就递归右儿子
两个都有就同时递归
3.[L,R]和[tl,tr]没有交集这种情况是不存在的
图中为查询[5,9]区间
时间复杂度大概为O(4logn)近似为O(logn)
modify操作
单点修改
直接递归到子节点x在pushup就行,因为是递归每个父区间都会pushup到,也即更新到
先把m个位置开好
那么第一个操作就变成修改第n+1位置的值
第二个操作就是问[ n-l+1 , n ] 的最大值
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int m,p;
struct node
{
int l,r;
int v;
}tr[N*4];
void pushup(int u)
{
tr[u].v=max(tr[u<<1].v,tr[u<<1|1].v);
}
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);
//pushup(u);由于是后面慢慢加点进来 所以build不用puahup
}
int query(int u,int l,int r)
{
if(tr[u].l>=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,int x,int v)
{
if(tr[u].l==x&&tr[u].r==x)
tr[u].v=v;
else
{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u);
}
}
int main()
{
int n=0;
int last=0;
scanf("%d%d",&m,&p);
build(1,1,m);
int x;
char op[2];
while(m--)
{
scanf("%s%d",op,&x);
if(op[0]=='Q')
{
last=query(1,n-x+1,n);
printf("%d\n",last);
}
else
{
modify(1,n+1,(last+x)%p);
n++;
}
}
return 0;
}
2.你能回答这些问题吗
1.修改是单点修改
2.查询区间内的最大子段和
那么结构体就要存(1)左端点(2)右端点(3)连续子段和(4)最大后缀和(5)最大前缀和(6)区间和
细节:
为什么query的ans不用赋初值?因为pushup就是更新ans用的
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,m;
int w[N];
struct Node
{
int l,r;
int tmux,lmax,rmax,sum;
}tr[4*N];//建线段树要开4倍空间
void pushup(Node& u,Node& l,Node& r)//用子节点信息更新父节点信息
{
u.sum=l.sum+r.sum;//和等于两个子段和
u.lmax=max(l.lmax,l.sum+r.lmax);//左边最长字段和 = 左子树的左边最长子段和 与 左子段的和+右边的左边最大子段和 取最大值
u.rmax=max(r.rmax,l.rmax+r.sum);//右边最长字段和 = 右子树的右边最长子段和 与 左子段的最大右子段和+右边子段和 取最大值
u.tmux=max(max(l.tmux,r.tmux),l.rmax+r.lmax);//最长连续子段和 = 左边最长子段和 与 右边最长子段和 与 左边的后面+左边的前面 取最大
}
void pushup(int u)//用子节点信息更新父节点信息
{
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r)//建一颗线段树
{
if(l==r) tr[u]={l,r,w[r],w[r],w[r],w[r]};
else
{
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);//建左右子区间
pushup(u);//更新一遍这个节点
}
}
Node query(int u,int l,int r)//询问从某个父节点开始,问l,r的区间最大连续子段和
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u];//假如这个父节点在区间中直接返回这个父节点的最大连续区间子段和
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid) return query(u<<1,l,r);//假如整个区间在左边
if(l>mid) return query(u<<1|1,l,r);//假如整个区间在右边
auto left=query(u<<1,l,r),right=query(u<<1|1,l,r);//反之左右都有
Node ans;
pushup(ans,left,right);//则求一边左右区间的最大值
return ans;//返回答案
}
void modify(int u,int x,int v)//修改某个位置的值
{
if(tr[u].l==x&&tr[u].r==x) tr[u]={x,x,v,v,v,v};//假如找到了这个位置,直接修改这个位置的值
else
{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);//假如这个值在左区间,则递归左区间进行查找这个值进行修改
else modify(u<<1|1,x,v);//反之在右区间,则递归右区间进行查找这个值进行修改
pushup(u);//修改了之后这个父节点的值也要更新一下
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
build(1,1,n);
int k,x,y;
while(m--)
{
scanf("%d%d%d",&k,&x,&y);
if(k&1)
{
if(x>y) swap(x,y);
printf("%d\n",query(1,x,y).tmux);//输出查找后的答案
}
else modify(1,x,y);//把x的位置的值改位y,递归处理
}
return 0;
}
3. 区间最大公约数
这里的线段树维护的是一个差分,假如添加则a[l]+=d,a[r+1]-=d
假如查询则gcd(a[l],gcd(b[l+1]~b[r])),也即a[l]这个元素与[l+1,r]这个区间的最大公约数取最大公约数
区间l,r的最大公约数等于差分后的数的最大公约数
结构体要存1.最大公约数2.总和
区间最大公约数
1.区间[l,r]增加一个数
-> 差分思想,将区间加减变成单点加减,可以不用lazy标记,用线段树或树状数组维护差分
2.求区间内最大公约数
储存信息
1.最大公约数
-> 若只有查询,可以由左子区间最大公约数和右子区间最大公约数,取他们的最大公约数得到
2.sum
维护差分序列 bi = ai - ai-1
gcd(a1, a2, a3…an) = gcd(a1, a2-a1, a3-a2 … an - an-1) = gcd(a1, gcd(b2, b3 … bn)) 代码中用w代替a
-> 线段树储存差分数组b,对于差分数组b,只需要修改两个点就可等同于对数组a进行区间修改
-> 变成单点修改,区间查询
对于每个询问,求出gcd(a[l]) 和 gcd(b[l+1] ~ b[r]),输出他们的最大公约数
-> gcd(a[l]) = gcd(b1 + b2 +…+ bl)
注意:
对于区间[l,r]内的最大公约数
query(1,l+1,r)求的是gcd(al+1 - al , al+2 - al+1… ar - ar-1)
而我们需要的是gcd(a1, a2-a1, a3-a2 … an - an-1)
故要在query(1,l+1,r)的基础上再与query(1,1,l)即gcd(a[l]),取最大公约数
转载于:这里
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
typedef long long ll;
int n,m;
ll w[N];
struct Node
{
int l,r;
ll sum,d;
}tr[4*N];
ll gcd(ll a,ll b)//求最大公约数
{
return b?gcd(b,a%b):a;
}
void pushup(Node& u,Node& l,Node& r)//用子节点信息更新父节点信息
{
u.sum=l.sum+r.sum;//父节点的和等于子节点的两个和的和
u.d=gcd(l.d,r.d);//父节点最小公倍数等于两个子节点的最小公倍数
}
void pushup(int u)//用来更新父节点信息
{
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void built(int u,int l,int r)//建立线段树
{
if(l==r)
{
ll b=w[l]-w[l-1];//换成差分数组
tr[u]={l,r,b,b};
}
else
{
tr[u]={l,r};
int mid=l+r>>1;
built(u<<1,l,mid),built(u<<1|1,mid+1,r);//建左右子树
pushup(u);//更新一遍节点信息
}
}
void modify(int u,int x,ll v)//用来更改信息
{
if(tr[u].l==x&&tr[u].r==x)//假如找到了这个点
{
ll sum=tr[u].sum+v;//这个点加上这个数
tr[u]={x,x,sum,sum};//更新
}
else
{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);//假如这个点在左边
else modify(u<<1|1,x,v);//假如这个点在右边
pushup(u);//更新一下节点信息
}
}
Node query(int u,int l,int r)//查找某个区间
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u];//假如这个区间在查找区间内,则返回
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid) return query(u<<1,l,r);//假如在左边,则查找左边
if(l>mid) return query(u<<1|1,l,r);//假如在右边,则查找右边
auto left=query(u<<1,l,r),right=query(u<<1|1,l,r);//反之在左右区间内
Node res;
pushup(res,left,right);//用左右区间更新一下答案
return res;//返回答案
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
built(1,1,n);
char op[2];
int l,r;
ll d;
while(m--)
{
scanf("%s",op);
if(*op=='Q')
{
scanf("%d%d",&l,&r);
auto id1=query(1,1,l);//先查找a[l],也即id1.sum
if(l+1<=n)//假如右边有数
{
auto id2=query(1,l+1,r);//则查找一遍右边的最大公约数
printf("%lld\n",abs(gcd(id1.sum,id2.d)));//求两个最大公约数
}
else printf("%lld\n",abs(id1.sum));//反之没有右边的数,直接输出
}
else
{
scanf("%d%d%lld",&l,&r,&d);
modify(1,l,d);//b[l]+=d
if(r+1<=n) modify(1,r+1,-d);//b[r+1]-=d
}
}
return 0;
}