简介
众所周知啊,与它齐名的还有个树状数组,那是一个简单亿倍的东西,问题不大。
线段树、是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界(毒瘤到炸),因此有时需要离散化让空间压缩。
这种东西啊,一般都处理可以运用结合律的东西(对,就是小学生都会的东西例如区间最值问题,或者是区间和什么的
例如这个,叶子结点就是单个的数,其中每个非叶子节点都是一段区间,并且也就是他儿子节点信息的整合(比如这里是min的问题)由此可见,线段树是二叉树。
建树
利用二叉树的性质可知一个点x的左右儿子分别是x*2和x*2+1,所以利用这个性质我们求出儿子
int ls(int p){return p<<1;}//左儿子 int rs(int p){return p<<1|1;}//又儿子
因为这个数是从下往上整理的,所以我们就可以搜索这个二叉树,一旦到了叶子结点,我们就可以给它赋值,等到一个节点的子节点做完了后,我们就可以整理
void push_up(int p) { ans[p]=ans[ls(p)]+ans[rs(p)];//整理子节点 } void build(int p,int l,int r)//节点编号和左右区间 { tag[p]=0;//标记清空 if(l==r)//叶子结点 { ans[p]=a[l];//赋值 return; } int mid=(l+r)>>1;//二分递归左右儿子 build(ls(p),l,mid); build(rs(p),mid+1,r); push_up(p);//整理信息 }
现在就是区间修改了(单点修改就是区间长为1的特殊的区间修改
这里要引入一个懒标记(lazy
为什么呢,因为如果每次区间修改都递归到叶子结点然后再向上传,这个时间复杂度是极高的
所以当我们找到修改的区间后,打上标记,后面做操作
因为当这个区间的值暂时不用的时候,下一次再修改这个区间,就可以直接累加,两次或者多次操作就可以成功地合并成一次操作,时间复杂度也就大大降低的,但是怎么操作呢?
void f(ll p,ll l,ll r,ll k) { tag[p]+=k;//记录标记 ans[p]+=k*(r-l+1);//并且更改值 } void push_down(ll p,ll l,ll r) { ll mid=(l+r)>>1; f(ls(p),l,mid,tag[p]); f(rs(p),mid+1,r,tag[p]); tag[p]=0;//标记下传,标记清空 } void add(int nl,int nr,int l,int r,int p,int k) {//修改左右区间,当前左右区间,当前节点编号,修改的值 if(l>=nl&&r<=nr)//找到区间我们就 { tag[p]+=k;//直接打标记 ans[p]+=k*(r-l+1);//因为区间内每个点都加上k,区间值也就元素个数*k return; } push_down(p,l,r);//这里有一个向下传递标记的作用,因为能到这里的区间有可能有一部分是要修改的,也就是说对于现在的这个区间 //是有影响的,,并且后面的查询有可能会用到这个值,所以要更新 ll mid=(l+r)>>1;//便利子节点 if(nl<=mid)add(nl,nr,l,mid,ls(p),k); if(mid1,r,rs(p),k); push_up(p);//更新当前结点的信息 }
为什么修改的时候要下传懒标记并且更新呢
当我们看当前区间的时候,要传到子区间,是因为当查询的时候,那一段并没有直接更改值(红色的因为是更改的,所以直接ans变了),为了保证黄色区间的正确性,我们就需要从子节点整合,而子节点恰好需要上面的节点来传递标记更新
而push_down在回溯之前是因为这样才能在向下的时候顺便传递懒标记,而push_up同理,也就是在回溯的时候顺便整理
询问和修改差不多,也是搜索区间直接返回值
ll answer(ll nl,ll nr,ll l,ll r,ll p) { ll res=0; if(nl<=l&&r<=nr)return ans[p];//在区间内直接返回 push_down(p,l,r);//以防懒标记未下传,可以想象成这个先answer一步整理数据(因为区间内改可以直接改,不用push_up) ll mid=(l+r)>>1;//左右儿子 if(nl<=mid)res+=answer(nl,nr,l,mid,ls(p)); if(mid1,r,rs(p)); return res; }
这个就是线段树的基本操作
1 #include2 #define ll long long 3 using namespace std; 4 const int N=1000005<<1; 5 ll n,m; 6 ll ans[N]; 7 ll tag[N]; 8 ll a[N]; 9 int ls(int p){return p<<1;} 10 int rs(int p){return p<<1|1;} 11 void push_up(ll p){ans[p]=ans[ls(p)]+ans[rs(p)];} 12 void f(ll p,ll l,ll r,ll k) 13 { 14 tag[p]+=k; 15 ans[p]+=k*(r-l+1); 16 } 17 void push_down(ll p,ll l,ll r) 18 { 19 ll mid=(l+r)>>1; 20 f(ls(p),l,mid,tag[p]); 21 f(rs(p),mid+1,r,tag[p]); 22 tag[p]=0; 23 } 24 void build(ll p,ll l,ll r) 25 { 26 tag[p]=0; 27 if(l==r) 28 { 29 ans[p]=a[l]; 30 return; 31 } 32 ll mid=(l+r)>>1; 33 build(ls(p),l,mid); 34 build(rs(p),mid+1,r); 35 push_up(p); 36 } 37 void add(ll nl,ll nr,ll l,ll r,ll p,ll k) 38 { 39 if(l>=nl&&r<=nr) 40 { 41 tag[p]+=k; 42 ans[p]+=k*(r-l+1); 43 return; 44 } 45 push_down(p,l,r); 46 ll mid=(l+r)>>1; 47 if(nl<=mid)add(nl,nr,l,mid,ls(p),k); 48 if(mid 1,r,rs(p),k); 49 push_up(p); 50 } 51 ll answer(ll nl,ll nr,ll l,ll r,ll p) 52 { 53 ll res=0; 54 if(nl<=l&&r<=nr)return ans[p]; 55 push_down(p,l,r); 56 ll mid=(l+r)>>1; 57 if(nl<=mid)res+=answer(nl,nr,l,mid,ls(p)); 58 if(mid 1,r,rs(p)); 59 return res; 60 } 61 int main() 62 { 63 scanf("%lld%lld",&n,&m); 64 for(int i=1;i<=n;i++)scanf("%lld",&a[i]); 65 build(1,1,n); 66 while(m--) 67 { 68 ll p,l,r,k; 69 scanf("%lld",&p); 70 switch(p) 71 { 72 case 1:{ 73 scanf("%lld%lld%lld",&l,&r,&k); 74 add(l,r,1,n,1,k); 75 break; 76 } 77 case 2:{ 78 scanf("%lld%lld",&l,&r); 79 printf("%lld\n",answer(l,r,1,n,1)); 80 break; 81 } 82 } 83 } 84 return 0; 85 }