权值线段树,动态开合
一,情景导入
- 维护在值域上的桶排序,在数据量不大的时候可以离线离散化
- 也可以使用动态开点线段数缩小空间(缩至
2
∗
n
−
1
2*n-1
2∗n−1),借而扩大桶的空间
不过对于更擅长单点修改,获得区间和的树状数组优越性无法提现 - 对(同一点)多种物品同时进行差分或者求和计数,考虑到每个点都开完整的统计桶数组太浪费,所以每个节点维护动态开点的权线(1-n 种物品的桶)
- 条件:合并(子树)的时候搞定差分和前缀和的过程
二,动态开点并权线
i,概述
- 1, 树上合并权线完成差分
- 2, O ( 1 ) O(1) O(1) 寻找最值数目(差分值)
- 3, 线段树上二分最值位置
- 4,输出最值pos种类
ii, 合并的条件
所用同级同位线段树结构题非数据因素均相同(l,r)
iii,合并具体操作
泛化概述
- 左右端点作为参数传递,不再存入
- 结构体 l,r 代表是左右儿子编号,idx全局实时申请
- r o t [ ] rot[] rot[] , 代表不同时刻线段树的根节点编号,方便索引
- 所谓动态开点就是涉及哪个区间就开哪个部分,其余的保持空或者继承上个版本
1,核心函数
build, 申请新的节点
int build()
{
++tot;
tr[tot].lc= tr[tot].rc= tr[tot].sum = 0;
return tot;
}
insert (插入),实现动态开点和(新)信息储存
- 递归终点为找到对应单点
- 找到有交集区间开始递归
- 传参包括 l,r
void insert(int &p,int l,int r,int val,int delta)
{
if(!p) p =build();
if(l==r)
{
tr[p].sum+= delta;
return ;
}
int mid = l+r>>1;
if(val<=mid) insert(tr[p].lc,l,mid,val,delta);
else insert(tr[p].rc,mid+1,r,val,delta);
up(p);
}
merge (合并),实现回收节点和(新旧)信息合并
- 新旧节点数据直接做和
- 传参为待合并节点根编号
- 维护最大值,准备线段树上二分最值
- 合并顺序不定,注意这点不同与可持久数据结构
int merge(int p,int q,int l,int r)
{
if(!p) return q;
if(!q) return p;
if(l==r)
{
tr[p].sum +=tr[q].sum;
return p;
}
int mid = l+r >>1;
tr[p].lc = merge(tr[p].lc,tr[q].lc,l,mid);
tr[p].rc = merge(tr[p].rc,tr[q].rc,mid+1,r);
up(p);
return p;
}
query(线段树上二分) 维护最大值,类似于spaly
- 返回了最值得序列位置(为离散化准备)
- 传递最值pos参数亦可,实际复杂度差别不大
int query(int p,int l,int r)
{
if(l==r)return l;
int mid = l+r >>1;
if(tr[tr[p].lc].sum>=tr[tr[p].rc].sum)return query(tr[p].lc,l,mid);
else return query(tr[p].rc,mid+1,r);
}