动态开点是主席树的先修课,算是为学习主席树做一个课前预习吧。
个人理解动态开点类似于二叉树的链表实现,有两个指针指向左右儿子。动态开点大致也是这样。以求区间最大值为例子,线段树中维护三个信息:
- 当前节点的左儿子节点编号
- 当前节点的右儿子节点编号
- 区间最大值
首先是建树,建树的函数如下
int build(){
++cnt;
c[cnt].l = 0;
c[cnt].r = 0;
c[cnt].max = -inf;
return cnt;
}
表示新增加了一个节点,新增加的节点的左右儿子都为0(空),将当前节点的编号返回出去。如果新增加了节点,一定是从父节点向下访问的时候发现没有节点,这是就需要新建一个儿子节点,那么父亲节点的左或者右儿子节点的编号就是当前新建的节点的编号了。
初始的时候,需要有一个树根,,新建一个点,将这个点作为树根就可以了。
在执行单点更新的时候,例如在下标为的位置更改值,更新函数和线段树类似,如下
void insert(int l, int r, int k, int ind, int d){
if (l == r){
c[k].max += d;
return;
}
int mid = (l + r) >> 1;
if (ind <= mid){
if (c[k].l == 0)c[k].l = build();
insert(l, mid, c[k].l, ind, d);
}
else{
if (c[k].r == 0)c[k].r = build();
insert(mid + 1, r, c[k].r, ind, d);
}
c[k].max = max(c[c[k].l].max, c[c[k].r].max);
}
如果找到这个点了就更新并返回,否则就继续找,找的时候还是将区间一分为二,看这个点位于哪个区间,就向当前节点的左右儿子节点的某一个走,走的时候判断一下,假设走的是左儿子节点,如果左儿子存在,就继续走,不存在的话就需要先建立一个左儿子节点才能继续走。剩下的就和线段树一样了。
查询也类似,不再赘述。
#include <bits/stdc++.h>
#define mem(a, b) memset(a, b, sizeof a)
using namespace std;
const int N = 310;
const int inf = 0x7fffffff;
struct p{
int l, r, max;
};
p c[N * 4];
int root, cnt;
int build(){
++cnt;
c[cnt].l = 0;
c[cnt].r = 0;
c[cnt].max = -inf;
return cnt;
}
void insert(int l, int r, int k, int ind, int d){
if (l == r){
c[k].max = d;
return;
}
int mid = (l + r) >> 1;
if (ind <= mid){
if (c[k].l == 0)c[k].l = build();
insert(l, mid, c[k].l, ind, d);
}
else{
if (c[k].r == 0)c[k].r = build();
insert(mid + 1, r, c[k].r, ind, d);
}
c[k].max = max(c[c[k].l].max, c[c[k].r].max);
}
int query(int ind, int k, int l, int r){
if (l == r){
return c[k].max;
}
int mid = (l + r) >> 1;
if (ind <= mid)return query(ind, c[k].l, l, mid);
else return query(ind, c[k].r, mid + 1, r);
}
int main()
{
root = build();
insert(1, 5, root, 2, 1);
insert(1, 5, root, 1, 2);
insert(1, 5, root, 3, 4);
insert(1, 5, root, 4, 3);
insert(1, 5, root, 5, 5);
for (int i = 1; i <= 5; i++){
printf("%d : %d\n",i, query(i, root, 1, 5));
}
return 0;
}