您的位置:首页 > 其它

线段树学习笔记

2015-10-29 20:26 225 查看
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。
以下笔记摘自lcomyn神犇博客(http://blog.csdn.net/lcomyn/article/details/40822229)
1.以单点更新,求区间最小值为例

自下而上更新:

[cpp] view
plaincopy

void updata(int i)  

{  

node[i].value=min(node[i*2].value,node[i*2+1].value);//代码核心,不同程序基本只有此处不同。  

}  

建树:  

 void build(int i,int l,int r)//建立区间为[l,r](注意为闭区间)  

{  

if (l==r)//已经找到叶子节点  

 {  

   node[i].value=a[l];  

   return;  

 }  

build(i*2,l,(l+r)/2);//建立左子树(注意区间范围)  

build(i*2+1,(l+r)/2+1,r);//建立右子树(注意区间范围)  

updata(i);//更新节点信息,注意先查找后更新。  

}  

单点更新:

[cpp] view
plaincopy

void insert(int i,int l,int r,int x,int y)  

{  

int mid;  

if ((r==l)&&(l==x))//已查询到此节点  

 {  

    node[i].value+=y;//更新  

   return;  

 }  

mid=(l+r)/2;  

if (x<=mid)  

 insert(i*2,l,mid,x,y);//在左子树  

else  

 insert(i*2+1,mid+1,r,x,y);//在右子树  

updata(i);//更新  

}

2.对于单点更新线段树中第几个非空叶节点(如joseph问题,POJ2828 Buy tickets等),我们可以用node[i].value来记录该区间有几个非空节点,查询时比较x与node[i*2].value即可。


[cpp] view
plaincopy

void insert(int i,int l,int r,int x)  

{  

if (l==r)  

 {  

   node[i].value=0;//修改  

   ans=l;  

   return;  

 }  

if (x<=node[i*2])//左子树中  

 insert(i*2,l,mid,x);  

else//右子树中,注意减去左子树中数目。  

 insert(i*2+1,mid+1,r,x-node[i*2]);  

updata(i);  

}   

处理[x,y]询问(PS:由于x,y不发生改变,亦可用全局变量)  

 void query(int i,int l,int r,int x,int y)//在节点i的[l,r]区间内查询[x,y]  

{  

int mid;  

if ((x<=l)&&(y>=r))//如果区间包含于其中,查询即可  

 {  

   ans=min(ans,node[i].value);  

   return;  

 }  

mid=(l+r)/2;  

if (x<=mid)//左子树有交集  

 query(i*2,l,mid,x,y);  

if (y>mid)//右子树有交集  

 query(i*2+1,mid+1,r,x,y);  

}   

对于区间修改(给区间整体加减乘实数除一个定值),区间查询类问题,我们可以对每一个节点设置一个delta,记录更新值,而不进行实质性更新,每当查询或询问到此节点时,在对delta进行下放,下放至左右子树,这样就保证了程序的效率;

建树,自下而上更新代码相同
1.区间修改,区间最值:

释放标记 

[cpp] view
plaincopy

void paint(int i,int  a)  

{  

     node[i].value+=a;  

     delta[i]+=a;  

}  

标记下放 

[cpp] view
plaincopy

void pushdown(int i)  

{  

     paint(i*2,delta[i]);//左子树  

     paint(i*2+1,delta[i]);//右子树  

     delta[i]=0;//释放delta  

}  

区间更新[x,y]

[cpp] view
plaincopy

void insert(int i,int l,int r,int x,int y,int a)  

{  

   int mid; mid=(l+r)/2;  

   if (x<=l&&y>=r)  

     {  

        paint(i,a);//修改该区间的value,记录delta;  

        return;  

     }  

   pushdown(i);//标记下放。  

   if (x<=mid)  

     insert(i*2,l,mid,x,y,a);  

   if (y>mid)  

     insert(i*2+1,mid+1,r,x,y,a);  

   updata(i);  

}  

处理[x,y]询问(PS:由于x,y不发生改变,亦可用全局变量)
 void query(int i,int l,int r,int x,int y)//在节点i的[l,r]区间内查询[x,y]

[cpp] view
plaincopy

{  

int mid;  

if ((x<=l)&&(y>=r))//如果区间包含于其中,查询即可  

 {  

   ans=min(ans,node[i].value);  

   return;  

 }  

 pushdown(i); //标记下放  

mid=(l+r)/2;  

if (x<=mid)//左子树有交集  

 query(i*2,l,mid,x,y);  

if (y>mid)//右子树有交集  

 query(i*2+1,mid+1,r,x,y);  

}   

2.值得一提的是,当区间最值改为区间求和时,node[i]应加上a*区间长度,所以paint和pushdown应多传递l和r两变量,对value值进行修改时 node[i].value+=a;改为node[i].value+=a*(r-l+1);value值不变
 3.对于给区间中的每一个值开平方抑或乘方等(即更新值不同),只能立即对标记下放至叶节点,但必须对更新条件加以判断,否则TLE
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: