使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。
以下笔记摘自lcomyn神犇博客(http://blog.csdn.net/lcomyn/article/details/40822229)
1.以单点更新,求区间最小值为例
自下而上更新:
- 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);//更新节点信息,注意先查找后更新。
- }
单点更新:
2.对于
单点更新
线段树中第几个非空叶节点(如joseph问题,POJ2828 Buy tickets等)
,我们可以用node[i].value来记录该区间有几个非空节点,查询时比较x与node[i*2].value即可。
- 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);//更新
- }
- 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.区间修改,区间最值:
释放标记
标记下放
区间更新[x,y]
释放标记
- void paint(int i,int a)
- {
- node[i].value+=a;
- delta[i]+=a;
- }
- void pushdown(int i)
- {
- paint(i*2,delta[i]);//左子树
- paint(i*2+1,delta[i]);//右子树
- delta[i]=0;//释放delta
- }
- 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]
- {
- 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