线段树学习笔记

线段树是一种 二叉搜索树 ,与 区间树 相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。

以下笔记摘自lcomyn神犇博客(http://blog.csdn.net/lcomyn/article/details/40822229)

1.以单点更新,求区间最小值为例
自下而上更新:

[cpp]  view plain copy
  1. void updata(int i)  
  2. {  
  3. node[i].value=min(node[i*2].value,node[i*2+1].value);//代码核心,不同程序基本只有此处不同。  
  4. }  
  5. 建树:  
  6.  void build(int i,int l,int r)//建立区间为[l,r](注意为闭区间)  
  7. {  
  8. if (l==r)//已经找到叶子节点  
  9.  {  
  10.    node[i].value=a[l];  
  11.    return;  
  12.  }  
  13. build(i*2,l,(l+r)/2);//建立左子树(注意区间范围)  
  14. build(i*2+1,(l+r)/2+1,r);//建立右子树(注意区间范围)  
  15. updata(i);//更新节点信息,注意先查找后更新。  
  16. }  
单点更新:
[cpp]  view plain copy
  1. void insert(int i,int l,int r,int x,int y)  
  2. {  
  3. int mid;  
  4. if ((r==l)&&(l==x))//已查询到此节点  
  5.  {  
  6.     node[i].value+=y;//更新  
  7.    return;  
  8.  }  
  9. mid=(l+r)/2;  
  10. if (x<=mid)  
  11.  insert(i*2,l,mid,x,y);//在左子树  
  12. else  
  13.  insert(i*2+1,mid+1,r,x,y);//在右子树  
  14. updata(i);//更新  
  15. }
2.对于 单点更新 线段树中第几个非空叶节点(如joseph问题,POJ2828 Buy tickets等) ,我们可以用node[i].value来记录该区间有几个非空节点,查询时比较x与node[i*2].value即可。
 
[cpp]  view plain copy
  1. void insert(int i,int l,int r,int x)  
  2. {  
  3. if (l==r)  
  4.  {  
  5.    node[i].value=0;//修改  
  6.    ans=l;  
  7.    return;  
  8.  }  
  9. if (x<=node[i*2])//左子树中  
  10.  insert(i*2,l,mid,x);  
  11. else//右子树中,注意减去左子树中数目。  
  12.  insert(i*2+1,mid+1,r,x-node[i*2]);  
  13. updata(i);  
  14. }   
  15. 处理[x,y]询问(PS:由于x,y不发生改变,亦可用全局变量)  
  16.  void query(int i,int l,int r,int x,int y)//在节点i的[l,r]区间内查询[x,y]  
  17. {  
  18. int mid;  
  19. if ((x<=l)&&(y>=r))//如果区间包含于其中,查询即可  
  20.  {  
  21.    ans=min(ans,node[i].value);  
  22.    return;  
  23.  }  
  24. mid=(l+r)/2;  
  25. if (x<=mid)//左子树有交集  
  26.  query(i*2,l,mid,x,y);  
  27. if (y>mid)//右子树有交集  
  28.  query(i*2+1,mid+1,r,x,y);  
  29. }   
对于区间修改(给区间整体加减乘实数除一个定值),区间查询类问题,我们可以对每一个节点设置一个delta,记录更新值,而不进行实质性更新,每当查询或询问到此节点时,在对delta进行下放,下放至左右子树,这样就保证了程序的效率;
建树,自下而上更新代码相同
1.区间修改,区间最值:
释放标记 
[cpp]  view plain copy
  1. void paint(int i,int  a)  
  2. {  
  3.      node[i].value+=a;  
  4.      delta[i]+=a;  
  5. }  
标记下放 
[cpp]  view plain copy
  1. void pushdown(int i)  
  2. {  
  3.      paint(i*2,delta[i]);//左子树  
  4.      paint(i*2+1,delta[i]);//右子树  
  5.      delta[i]=0;//释放delta  
  6. }  
区间更新[x,y]
[cpp]  view plain copy
  1. void insert(int i,int l,int r,int x,int y,int a)  
  2. {  
  3.    int mid; mid=(l+r)/2;  
  4.    if (x<=l&&y>=r)  
  5.      {  
  6.         paint(i,a);//修改该区间的value,记录delta;  
  7.         return;  
  8.      }  
  9.    pushdown(i);//标记下放。  
  10.    if (x<=mid)  
  11.      insert(i*2,l,mid,x,y,a);  
  12.    if (y>mid)  
  13.      insert(i*2+1,mid+1,r,x,y,a);  
  14.    updata(i);  
  15. }  
处理[x,y]询问(PS:由于x,y不发生改变,亦可用全局变量)
  void query(int i,int l,int r,int x,int y)//在节点i的[l,r]区间内查询[x,y]
[cpp]  view plain copy
  1. {  
  2. int mid;  
  3. if ((x<=l)&&(y>=r))//如果区间包含于其中,查询即可  
  4.  {  
  5.    ans=min(ans,node[i].value);  
  6.    return;  
  7.  }  
  8.  pushdown(i); //标记下放  
  9. mid=(l+r)/2;  
  10. if (x<=mid)//左子树有交集  
  11.  query(i*2,l,mid,x,y);  
  12. if (y>mid)//右子树有交集  
  13.  query(i*2+1,mid+1,r,x,y);  
  14. }   
2.值得一提的是,当区间最值改为区间求和时,node[i]应加上a*区间长度,所以paint和pushdown应多传递l和r两变量,对value值进行修改时 node[i].value+=a;改为node[i].value+=a*(r-l+1);value值不变
 3.对于给区间中的每一个值开平方抑或乘方等(即更新值不同),只能立即对标记下放至叶节点,但必须对更新条件加以判断,否则TLE

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值