大纲
1.线段树思想
2.普通线段树操作函数
3.存储线段树
4.普通线段树操作实现
5.线段树常见搭配方式
6.进阶线段树-乘法线段树
7.进阶线段树-根号线段树
8.线段树解决问题
1.线段树思想
.......
2.线段树操作函数
1.pushup函数
2.pushdown
3.build
4.modify
5.query
3.存储线段树
1.线段树的儿子存储
根节点的编号为1,接下来顺序编号即可。
2.线段树的节点信息存储
struct Tree{ int val; int l,r; } tr[maxm<<2];结构体存储线段树,别忘了数组开4倍。
4.线段树函数实现
1.建树函数build
bool is_leaf(int l,int r){ return l==r; }
bool is_leaf(int l,int r){ return l==r; } void build(int p,int l,int r){ tr[p].l=l,tr[p].r=r; if(is_leaf(l,r)){ tr[i].val=v[l]; return; } int mid=l+r>>1; build(p<<1,l,mid),build(p<<1|1,mid+1,r); //tr[p]=f(tr[p<<1].val,tr[p<<1|1].val); pushup(p); }
2.查询函数query
2.1区间查询
1.如果当前查的区间在查询的区间里面,那么直接get它的val即可。 2.如果当前查的区间的左儿子和查询的区间有交集,那么直接查左儿子。 3.如果当前查的区间的右儿子和查询的区间有交集,那么直接查右儿子。
int query(int p,int l,int r){
if(tr[p].l>=l&&tr[p].r<=r)return tr[p].val; //情况1
if(tr[p].r<l||tr[p].l>r)return 0; //情况4
int s=0; //那么计算它的子节点query即可, 用s累加
if(tr[p<<1].r>=l)s+=query(p<<1,l,r);
if(tr[p<<1|1].l<=r)s+=query(p<<1|1,l,r);
return s; //传输结果
}
2.2单点查询
操作①: 每次选择一个包含目标的儿子节点,进行查。 重复操作①,如果遍历到了叶子节点,get它的val即可。
int mv,index; void get_val(int p,int l,int r){ if(l==r){ //get(); mv=tr[p].val; return; } int ls=p<<1,rs=p<<1|1,mid=l+r>>1; if(index>=tr[ls].l&&index<=tr[ls].r)get_val(ls,l,mid); if(index>=tr[rs].l&&index<=tr[rs].r)get_val(rs,mid+1,r); }
3.修改函数modify 3.1单点修改
没问题了这个时候请跳转到pushup函数。
void modify(int p,int d,int k){
if(tr[p].l==tr[p].r){
//modify();
tr[i].val+=k; return;
}
if(d<=tr[i<<1].r)modify(i<<1,d,k);
else modify(i<<1|1,dis,k);
//pushup();
//tr[i].val=f(tr[i<<1].val,tr[i<<1|1].val);
tr[i].val=tr[i<<1].val+tr[i<<1|1].val; return;
}
3.2区间修改
就是首先将真个包含的区间的val直接改变(+=k),对于其他的零散的区间,如果有完整的区间,那么就使用整的区间,否则对于那些单个的元素直接修改。
void modify(int p,int l,int r,int k){
if(tr[p].l>=l&&tr[p].r<=r){ //完全包含
tr[p].val+=k;
return;
}
int mid=tr[p].l+tr[p].r>>1;
/*
.决策.左右儿子
*/
if(l<=mid)modify(p<<1,l,r,k);
if(r>mid)modify(p<<1|1,l,r,k);
}
4.pushup 为什么要在末尾呢?因为能进到当前这个modify函数里一定哈没有找到目标的index所以要继续往下找,到了深度更深的层次,找到了的时候它是这条路径上的一份子,所以根据规则进行自己节点的val值修改。 5.pushdown
void pushdown(int i){
if(tr[i].lz!=0){
tr[i<<1].lz+=tr[i].lz;
tr[i<<1|1].lz+=tr[i].lz;
int mid=(tr[i].l+tr[i].r)/2;
tr[i<<1].sum+=tr[i].lz*(mid-tr[i<<1].l+1);
tr[i<<1|1].sum+=tr[i].lz*(tr[i<<1|1].r-mid);
tr[i].lz=0;
}
}
5.线段树常见搭配方式
(1).单点修改&区间查询
太简单了
(2).区间修改&单点查询
模板
#include<bits/stdc++.h>
#define FOR(x,y,z) for(int x=y,x_=z;x<=x_;x++)
#define DOR(x,y,z) for(int x=y,x_=z;x>=x_;x--)
#define ll long long
using namespace std;
void read(int& x){
char c;x=0;
int f=1;
while(c=getchar(),c<'0'||c>'9')if(c=='-')f=-1;
do x=(x<<3)+(x<<1)+(c^48);
while(c=getchar(),c>='0'&&c<='9');
x*=f;
}
void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+48);
}
const int maxn=5e5+10;
int n,m,s,t,T,a[maxn];
namespace Seg{
struct Node{
int l,r; int val;
}tr[maxn<<2];
void build(int p,int l,int r){
tr[p]={l,r,0}; //初始化
if(l==r){ //叶子节点没有子节点
tr[p].val=a[l]; //直接赋值
return;
}
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
//建立子节点
}
void modify(int p,int l,int r,int k){
if(tr[p].l>=l&&tr[p].r<=r){ //一个路径都要加
tr[p].val+=k;
return;
}
int mid=tr[p].l+tr[p].r>>1;
if(l<=mid)modify(p<<1,l,r,k);
if(r>mid)modify(p<<1|1,l,r,k);
//决策左右儿子
}
void query(int p,int index){
// void get_()
T+=tr[p].val;
if(tr[p].l==tr[p].r)return; //子节点
int mid=tr[p].l+tr[p].r>>1;
if(index<=mid)query(p<<1,index);
else query(p<<1|1,index);
}
}
int main(){
read(n),read(m);
FOR(i,1,n)read(a[i]);
Seg:: build(1,1,n); //建树
FOR(i,1,m){ //操作
int F; read(F);
if(F==1){
int t,x,y; read(t),read(x),read(y);
Seg:: modify(1,t,x,y);
}else{
T=0;
int x; read(x);
Seg:: query(1,x);
write(T),putchar('\n');
}
}
}
6.进阶线段树-乘法线段树
当传递懒惰标记的时候,因为有乘和加,所以lazer_tage要判断乘和加的顺序,需要处理一下
这里的懒惰标记就分为plz(plus_lazer_tage)和mlz(mul_lazer_tage)。
mul_lazer_tage很简单,直接乘父节点即可。
plus_lazer_tage:plus_lazer_tage(i)*mul_lazer_tage(fa(i))+plus_lazer_tage(fa(i))
void pushdown(long long i){
long long k1=tree[i].mlz,k2=tree[i].plz;
tree[i<<1].sum=(tree[i<<1].sum*k1+k2*(tree[i<<1].r-tree[i<<1].l+1))%p;
tree[i<<1|1].sum=(tree[i<<1|1].sum*k1+k2*(tree[i<<1|1].r-tree[i<<1|1].l+1))%p;
tree[i<<1].mlz=(tree[i<<1].mlz*k1)%p;
tree[i<<1|1].mlz=(tree[i<<1|1].mlz*k1)%p;
tree[i<<1].plz=(tree[i<<1].plz*k1+k2)%p;
tree[i<<1|1].plz=(tree[i<<1|1].plz*k1+k2)%p;
tree[i].plz=0;
tree[i].mlz=1;
return;
}
7.根号线段树
其中,lazy_tage把除法当成减法,记录的是这个区间里每个元素减去的值。
void Sqrt(int i,int l,int r){
if(tr[i].l>=l&&tr[i].r<=r&&(tr[i].minn-(long long)sqrt(tr[i].minn))==(tr[i].maxx-(long long)sqrt(tr[i].maxx))){
long long u=tr[i].minn-(long long)sqrt(tr[i].minn);
tr[i].lz+=u;
tr[i].sum-=(tr[i].r-tr[i].l+1)*u;
tr[i].minn-=u;
tr[i].maxx-=u;
return ;
}
if(tr[i].r<l||tr[i].l>r)return;
push_down(i);
if(tr[i<<1].r>=l)Sqrt(i<<1,l,r);
if(tr[i<<1|1].l<=r)Sqrt(i<<1|1,l,r);
tr[i].sum=tr[i<<1].sum+tr[i<<1|1].sum;
tr[i].minn=min(tr[i<<1].minn,tr[i<<1|1].minn);
tr[i].maxx=max(tr[i<<1].maxx,tr[i<<1|1].maxx);
return;
}
8.线段树解决问题
最大公约数也具有合并性,比如:
#include<bits/stdc++.h>
#define FOR(x,y,z) for(int x=y,x_=z;x<=x_;x++)
#define DOR(x,y,z) for(int x=y,x_=z;x>=x_;x--)
#define ll long long
using namespace std;
void read(ll& x){
char c;x=0;
int f=1;
while(c=getchar(),c<'0'||c>'9')if(c=='-')f=-1;
do x=(x<<3)+(x<<1)+(c^48);
while(c=getchar(),c>='0'&&c<='9');
x*=f;
}
void write(ll x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+48);
}
const int maxn=5e5+10,maxl=5;
ll n,m; ll a[maxn];
struct Node{
int l,r;
ll val,d;
}tr[maxn<<2];
ll gcd(ll a,ll b){
return b?gcd(b,a%b):a;
}
void pushup(Node& u,Node& l,Node& r){
u.val=l.val+r.val,u.d=gcd(l.d,r.d);
}
void build(int u,int l,int r){
if(l==r)tr[u]={l,r,a[r]-a[r-1],a[r]-a[r-1]};
else{
tr[u].l=l,tr[u].r=r;
int mid=l+r>>1; build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
}
void modify(int u,int x,ll vals){
if(tr[u].l==x&&tr[u].r==x)tr[u]={x,x,tr[u].val+vals,tr[u].val+vals};
else{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid)modify(u<<1,x,vals);
else modify(u<<1|1,x,vals);
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
}
Node query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r)return tr[u];
else{
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid)return query(u<<1,l,r);
else if(l>mid)return query(u<<1|1,l,r);
else{
Node lt=query(u<<1,l,r),rt=query(u<<1|1,l,r),Tr;
pushup(Tr,lt,rt); return Tr;
}
}
}
int main(){
read(n),read(m);
FOR(i,1,n)read(a[i]);
build(1,1,n);
int l,r; ll d;
char q[maxl];
FOR(i,1,m){
scanf("%s%d%d",q,&l,&r);
if(*q=='Q'){
Node lt=query(1,1,l),rt;
rt={0,0,0,0};
if(l+1<=r)rt=query(1,l+1,r);
write(abs(gcd(lt.val,rt.d))),putchar('\n');
}else{
read(d);
modify(1,l,d); if(r+1<=n)modify(1,r+1,d *-1);
}
}
}