线段树模板 求区间和, 区间加法,乘法更新

今天再次入门线段树

有了一点点感觉

线段树在有结合律性质的区间操作都可以用也许

然而我并不会


#include<bits/stdc++.h>

#define ll long long
#define INF 0x3f3f3f3f
#define FOR(i,n)    for(int i = 1 ; (i)<=(n);++(i))
#define lson(i) (i<<1)
#define rson(i) ((i<<1)|1)
using namespace std;

const int maxn = 500010+10;
ll a[maxn];
ll modp;
struct node{
    ll l,r;
    ll val,tag;
    ll mut  ;
}seg[maxn*4];

// 儿子更新父亲
void push_up(ll i){
    seg[i].val = (seg[lson(i)].val+seg[lson(i)|1].val)%modp;
}
// 建树
void build(ll i,ll l,ll r){
    seg[i].l = l;
    seg[i].r = r;
    seg[i].mut = 1;
    if(l==r){
        seg[i].val = a[l]%modp;
        return;
    }
    ll mid = (l+r)>>1;
    build(lson(i),l,mid);
    build(rson(i),mid+1,r);
    push_up(i);
}
// 下发懒标记
void push_down(ll p){
    if(seg[p].mut!=1){  //乘法标记
        seg[lson(p)].val = (seg[p].mut*seg[lson(p)].val)%modp;
        seg[rson(p)].val = (seg[p].mut*seg[rson(p)].val)%modp;

        seg[lson(p)].mut = (seg[p].mut*seg[lson(p)].mut)%modp;
        seg[rson(p)].mut = (seg[p].mut*seg[rson(p)].mut)%modp;

        seg[(lson(p))].tag = (seg[p].mut*seg[lson(p)].tag)%modp;
        seg[rson(p)].tag = (seg[p].mut*seg[rson(p)].tag)%modp;

        seg[p].mut = 1;
    }
    if(seg[p].tag){    //加法标记
        seg[lson(p)].val =(seg[lson(p)].val+seg[p].tag*(seg[lson(p)].r - seg[lson(p)].l+1))%modp;
        seg[rson(p)].val =(seg[rson(p)].val+seg[p].tag*(seg[rson(p)].r - seg[rson(p)].l+1))%modp;
        seg[lson(p)].tag = (seg[lson(p)].tag+seg[p].tag)%modp;
        seg[rson(p)].tag = (seg[rson(p)].tag+seg[p].tag)%modp;
        seg[p].tag = 0;
    }
}
// 加法更新
void update(ll i,ll l,ll r,ll val){
    if(seg[i].l >= l && seg[i].r <= r){ // 覆盖区间 直接更新
        seg[i].tag = (val+seg[i].tag)%modp; 
        seg[i].val = (val*(seg[i].r-seg[i].l+1)+seg[i].val)%modp;
        return;
    }
    if(seg[i].mut!=1 || seg[i].tag)
        push_down(i);
    ll mid = (seg[i].l + seg[i].r) >> 1;
    if(l<=mid)  update(lson(i),l,r,val);
    if(r>mid) update(rson(i),l,r,val);
    push_up(i);
}
// 乘法更新
void updateMut(ll i,ll l,ll r,ll mut){
    if(seg[i].l >= l && seg[i].r <= r){ // 覆盖区间 直接更新
        seg[i].mut = (mut*seg[i].mut)%modp;
        seg[i].tag = (mut*seg[i].tag)%modp;
        seg[i].val = (mut*seg[i].val)%modp;
        return;
    }
    if(seg[i].mut!=1 || seg[i].tag)
        push_down(i);
    ll mid = (seg[i].l + seg[i].r) >> 1;
    if(l<=mid)  updateMut(lson(i),l,r,mut);
    if(r>mid)   updateMut(rson(i),l,r,mut);
    push_up(i);
}
// 查询
ll query(ll i,ll l,ll r){
    if(seg[i].l >= l &&seg[i].r <= r){
        return seg[i].val%modp;
    }
    if(seg[i].mut!=1 || seg[i].tag)
        push_down(i);
    ll mid = (seg[i].l + seg[i].r) >> 1;
    ll res = 0;
    if(l<=mid)  res+= query(lson(i),l,r);
    if(r>mid)   res+= query(rson(i),l,r);
    return res%modp;
}

ll n,m;

int main(){
    cin >>n >> m >>modp;
    FOR(i,n)cin >>a[i];
    build(1,1,n);
    ll op,x,y;
    ll k;
    FOR(i,m){
        cin >> op >>x >> y;
        if(op==1){
            cin >>k;
            updateMut(1,x,y,k);
        }else if(op==2){
            cin >> k;
            update(1,x,y,k);
        }else{
            cout << query(1,x,y)%modp <<endl;
        }
    }

    return 0;
}

结构体比数组更加直观一点,调用的时候也不用传那么多参数,就是引用成员代码有点长(然后右儿子写成了左儿子完全看不出来,wa了半小时QAQ)

总结下线段树的特点

  • 每个节点都代表了一个区间的信息(长度为1的区间为叶子节点,代表一个点),可以方便的进行区间操作
  • 树的结构为完全二叉树,递归的性质很好,也有左右儿子的性质
  • 区间更新时的懒标记,当前区间被更新区间所覆盖,则直接在当前区间更新打上懒标记后返回,子区间只会在对其操作时通过懒标记再更新。
  • 多个操作要注意之间的影响(乘法对加法有影响,要更新加法的懒标记)
posted @ 2019-02-17 21:42  新新人類  阅读(246)  评论(0编辑  收藏  举报