线段树学习笔记
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
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为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
相关文章推荐
- Populating Next Right Pointers in Each Node II
- NOIP模拟10.28总结
- 英语单词state和status的区别
- linux常用命令加实例大全
- 为何机器学习的黄金时代才刚刚来临
- iOS viewController添加导航条以及返回跳转选择
- linux 常用命令
- R语言的基本矩阵运算
- 关于Xcode7以后ARC和MRC的一些问题的解决办法
- Linux硬链接和软链接区别
- android中关于省市县地址大全
- 浅谈堆和栈的区别
- 1014--C语言文法定义
- 数据库设计的三大范式
- 循环语句的使用
- Mac OS 提高工作效率的几个快捷键
- Write Amplification(翻译从维基百科)
- 单例设计模式
- ORACLE 常用的SQL语法和数据对象
- 去除字符串中的空格