线段树模板

线段树用于区间的修改统计问题,能用线段树维护的东西,必须满足区间可加性,即可用左右子结点推出父节点。
但其实线段树的用法远不止这些,这里来总结线段树的基本模板写法。
首先是递归建树 需要注意的地方是存树的数组要开4倍大小
push_up函数用来更新结点,这里以区间求和为例

inline void push_up(int k){
	t[k]=t[k<<1]+t[k<<1|1]; 
}
inline void build(int l,int r,int root){
	if(l==r){
		t[root]=a[l];  // a[i] 是原数组 
		return ;      //不要忘了return 
	}
	int mid=(l+r)/2;
	build(l,mid,root<<1);
	build(mid+1,r,root<<1|1);   //位运算更快,相当于root*2+1
	push_up(root);       //更新结点 
}

在输入初始a数组后调用build(1,n,1)就行了
然后就是单点修改操作

inline void add(int q,int v,int l,int r,int root){  // q为更新结点,v为更新值 
	if(l==r){
		t[root]+=v;
		return ;
	}
	int mid=(l+r)/2;
	if(q<=mid){
		add(q,v,l,mid,root<<1);   //递归更新子结点 
	}
	else {
		add(q,v,mid+1,r,root<<1|1);
	}
	push_up(root);   // 子结点更新后不要忘记更新父节点 
	
}

然后就是区间查询操作

inline int query(int fl,int fr,int l,int r,int root){  //fl,fr为查询区间 
	if(fl<=l&&fr>=r){
		return t[root];     //结点完全被包含在查询区间内 
	}
	int mid=(l+r)/2;
	int ans=0;
	if(fl<=mid)ans+=query(fl,fr,l,mid,root<<1);    //递归查询 
	if(fr>mid)ns+=query(fl,fr,mid+1,r,root<<1|1);

	return ans;
} 

可以写一写这个模板题 hdoj 1166

对于升级版的题目,需要进行区间修改,这个时候逐个进行单点修改肯定是不行的,就需要加入一个lazytag了,就是只有查询到当前区间才把标记下传,这样就大大减少了修改时间。
而核心操作就是push_down操作了

inline void  push_down(int k){ //以区间加法为例
    if(t[k].flag){
        t[k<<1].flag+=t[k].flag;   //继承父标记
        t[k<<1|1].flag+=t[k].flag;   //加法标记可加性
        t[k<<1].val+=(t[k<<1].r-t[k<<1].l+1)*t[k].flag;
        t[k<<1|1].val+=(t[k<<1|1].r-t[k<<1|1].l+1)*t[k].flag;
        t[k].flag=0;  //标记下穿后要清零
    }
}

为了方便操作,也把数组改成了结构体
可以写一下洛谷的这个模板题洛谷P3327
下面附上AC代码,可以当作区间修改模板

#include <bits/stdc++.h>
using namespace std;
const int MAX=1e5+5;
typedef long long ll;
struct node {
    ll val;
    int l,r;
    int flag;
}t[4*MAX];
int a[MAX];
inline void push_up(int k){
    t[k].val=t[k<<1].val+t[k<<1|1].val;
    return ;
}
inline void build(int l,int r,int root){
    t[root].l=l;
    t[root].r=r;
    if(l==r){
        t[root].val=a[l];
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,root<<1);
    build(mid+1,r,root<<1|1);
    push_up(root);
}

inline void  push_down(int k){
    if(t[k].flag){
        t[k<<1].flag+=t[k].flag;
        t[k<<1|1].flag+=t[k].flag;
        t[k<<1].val+=(t[k<<1].r-t[k<<1].l+1)*t[k].flag;
        t[k<<1|1].val+=(t[k<<1|1].r-t[k<<1|1].l+1)*t[k].flag;
        t[k].flag=0;
    }
}

inline void add(int fl,int fr,int v,int l,int r,int root){
    if(fl<=l&&fr>=r){
        t[root].val+=(t[root].r-t[root].l+1)*v;
        t[root].flag+=v;
        return ;
    }
    push_down(root);
    int mid=(l+r)/2;
    if(fl<=mid) add(fl,fr,v,l,mid,root<<1);
    if(fr>mid)  add(fl,fr,v,mid+1,r,root<<1|1);
    push_up(root);
}

inline ll query(int fl,int fr,int l,int r,int root){
    ll ans=0;
    if(fl<=l&&fr>=r){
        return t[root].val;
    }
    push_down(root);
    int mid=(l+r)/2;
    if(fl<=mid) ans+=query(fl,fr,l,mid,root<<1);
    if(fr>mid) ans+=query(fl,fr,mid+1,r,root<<1|1);
    return ans;
}
int main (){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    build(1,n,1);

    int a,x,y,z;
    for(int i=0;i<m;i++){
        scanf("%d",&a);
        if(a==1){
            cin>>x>>y>>z;
            add(x,y,z,1,n,1);
        }else {
            cin>>x>>y;
            printf("%lld\n",query(x,y,1,n,1));
        }
    }

    return 0;
}

最后就是洛谷的线段树模板题二,乘法操作和加法操作同时存在,要注意先进行乘法操作,这要可以方便对加法标记进行修改,下面直接附上AC代码,可当作模板使用

#include <bits/stdc++.h>
using namespace std;
const int MAX=1e5+5;
int a[MAX];
int p;
typedef long long ll;
struct node{
    ll val;
    ll l,r;
    ll flag;
    ll flagm;
}t[4*MAX];
inline void push_up(int k){
    t[k].val=(t[k<<1|1].val+t[k<<1].val)%p;
    return ;
}
inline void build(int l,int r,int root){
    t[root].l=l;
    t[root].r=r;
    t[root].flagm=1;
    if(l==r){
        t[root].val=a[l];
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,root<<1);
    build(mid+1,r,root<<1|1);
    push_up(root);
}
inline void push_down(int k){
    
    if(t[k].flagm!=1){
        t[k<<1].val=t[k<<1].val*t[k].flagm%p;
        t[k<<1|1].val=t[k<<1|1].val*t[k].flagm%p;
        t[k<<1].flagm=t[k<<1].flagm*t[k].flagm%p;
        t[k<<1|1].flagm=t[k<<1|1].flagm*t[k].flagm%p;
        t[k<<1].flag=(t[k<<1].flag*t[k].flagm)%p;
        t[k<<1|1].flag=(t[k<<1|1].flag*t[k].flagm)%p;
        t[k].flagm=1;
    }
	if(t[k].flag){
        t[k<<1].val=(t[k<<1].val+(t[k<<1].r-t[k<<1].l+1)*t[k].flag)%p;
        t[k<<1|1].val=(t[k<<1|1].val+(t[k<<1|1].r-t[k<<1|1].l+1)*t[k].flag)%p;
        t[k<<1].flag+=t[k].flag;
        t[k<<1|1].flag+=t[k].flag;
        t[k].flag=0;
    }

}
inline void add(int fl,int fr,int k,int l,int r,int root){
    if(fl<=l&&fr>=r){
        t[root].val=(t[root].val+(t[root].r-t[root].l+1)*k)%p;
        t[root].flag+=k;
        return ;
    }
    push_down(root);
    int mid=(l+r)/2;
    if(fl<=mid) add(fl,fr,k,l,mid,root<<1);
    if(mid<fr) add(fl,fr,k,mid+1,r,root<<1|1);
    push_up(root);

}
inline void mut(int fl,int fr,int k,int l,int r,int root){
    if(fl<=l&&fr>=r){
        t[root].val=(t[root].val*k)%p;
        t[root].flagm=(k*t[root].flagm)%p;
        t[root].flag=(k*t[root].flag)%p;
        return ;
    }
    push_down(root);
    int mid=(l+r)/2;
    if(fl<=mid) mut(fl,fr,k,l,mid,root<<1);
    if(fr>mid) mut(fl,fr,k,mid+1,r,root<<1|1);
    push_up(root);
}

inline ll query(int fl,int fr,int l,int r,int root){
    ll ans=0;
    if(fl<=l&&fr>=r){
        ans=t[root].val;
        return ans;
    }
    push_down(root);
    int mid=(l+r)/2;
    if(fl<=mid) ans+=query(fl,fr,l,mid,root<<1);
    if(fr>mid) ans+=query(fl,fr,mid+1,r,root<<1|1);
    return ans;
}
int main(){
    int n,m;
    cin>>n>>m>>p;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    build(1,n,1);
    int op,x,y,k;
    for(int i=0;i<m;i++){
        cin>>op;
        if(op==1){
            cin>>x>>y>>k;
            mut(x,y,k,1,n,1);
        }
        else if(op==2){
            cin>>x>>y>>k;
            add(x,y,k,1,n,1);
        }
        else {
            cin>>x>>y;
            cout<<query(x,y,1,n,1)%p<<endl;
        }
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值