假设操作次数为q,维护区间大小为m
普通的线段树会先把所有可能需要的节点开辟出来
需要的空间为O(4*m)
这样可以:
1.方便的通过节点下标所引导对应的左右儿子节点
2.所有需要的节点都已经有了,不必在额外创建新节点
动态开点线段树则是只开辟需要的节点,因此每个节点左右儿子都要记录
需要的空间为O(q*logm)
这样可以:
1.很明显可以节省空间
2.由于所需空间小,可以维护更大的区间
首先明确:线段树维护的区间和节点下标没有关系(普通线段树也是),维护的区间范围是什么是通过二分递归获得的。
没弄清的估计也只有我了。
原先的线段树,我们需要原先的区间被不断二分直到叶子的途中每一段都需要一个节点来记录信息
要维护的区间如果太大,我们就得开更大的空间以保证能维护途中所有节点,也就是说,空间限制了我们维护区间的大小
如图为区间大小为5所需的节点个数
而动态开点线段树不需要,对于一次单点修改,我们只需新建维护logm个节点:如图是区间大小为16,单点修改区间位置为9时,我们只涉及这几个点。
很显然,单点修改q次,则设计区间最多qlogm个。
如果是区间修改,虽然每一层不止开辟一个节点,但是由于整段包含在询问区间时直接lazy标记,最后可能均摊下来也是qlogm左右?这个我不太会证。
题目传送门
显然这题可以离散化 + 普通线段树/树状数组解决,而且也很方便,但是这题也可以用动态开点线段树写。
做法就是维护1e9的区间,每一个数都线段树在对应位置标记,按顺序每个数加入前,区间询问线段树中比当前数大的个数即可。
感觉没有啥坑点就不一块块代码讲了,直接贴个整题的代码。自己写写有问题在对拍一下吧。
对于各种垃圾#define 感到抱歉,但是我是真的懒。
代码:
#include
using namespace std;
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i>1
#define tl tree[rt].l
#define tr tree[rt].r
const int N = 5e5+5;
const int maxn = 500000*32 + 5;
const int MAXR = 1e9;
struct node
{
int l,r;
int sum;
}tree[maxn];
int sz;
void init(){sz = 2;}
void push_up(int rt)
{
tree[rt].sum = tree[tl].sum + tree[tr].sum;
}
void update(int p,int& rt,int l,int r)
{
//printf("update[%d,%d]\n",l,r);
if (!rt){
rt = sz++;
tl = tr = tree[rt].sum = 0;
}
if (l==r){
tree[rt].sum++;
return;
}
mid;
if (p<=m) update(p,tl,l,m);
else update(p,tr,m+1,r);
push_up(rt);
}
ll query(int L,int R,int rt,int l,int r)
{
//printf("query[%d,%d]\n",l,r);
if (!rt) return 0;
if (L<=l && r<=R) return tree[rt].sum;
ll ans = 0;
mid;
if (L<=m) ans += query(L,R,tl,l,m);
if (R>m) ans += query(L,R,tr,m+1,r);
return ans;
}
int main()
{
init();
int n;
scanf("%d",&n);
ll ans = 0;
int root = 1;
for1(i,1,n){
int x;
scanf("%d",&x);
if (x+1<=MAXR) ans += query(x+1,MAXR,1,1,MAXR);//防止区间无效
update(x,root,1,MAXR);
}
printf("%lld\n",ans);
return 0;
}