今天再次入门线段树
有了一点点感觉
线段树在有结合律性质的区间操作都可以用也许
然而我并不会
#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的区间为叶子节点,代表一个点),可以方便的进行区间操作
- 树的结构为完全二叉树,递归的性质很好,也有左右儿子的性质
- 区间更新时的懒标记,当前区间被更新区间所覆盖,则直接在当前区间更新打上懒标记后返回,子区间只会在对其操作时通过懒标记再更新。
- 多个操作要注意之间的影响(乘法对加法有影响,要更新加法的懒标记)