动态开点线段树——求逆序对数


一、与普通线段树的比较

空间区别

之前学习的线段树空间大小为O(4*n),根据值域的大小而开辟不同大小的空间,一开始我们就需要为线段树上的每一个点都分配一个空间,这就导致普通线段树不适合解决值域过大的问题。
那么反过来想,我们能否建立一个可以动态分配空间的线段树,把点分给我们实际上使用的位置,这样的话当点的个数不算太多时,我们就可以省下大量的空间,同时又满足我们解决问题


二、过程分析

以存入数列{5、4、2、6、3、1}为例,值域范围取1到10:
我们先给定节点的结构:

struct node{
	int l,r,sum;
};

一开始,我们先有一个根节点,且根节点的左右子树下标均指向0,意味着此处为空。
在这里插入图片描述

当存入数据5时,通过第一次二分确定了其范围在1到5之间,也就是左子树中,所以我们接下来会进入左子树,同时会传入左子树的地址作为下一次的根节点。
进入左子树后,我们首先发现了此时的根节点所处下标为0,即意味着当前点是不存在的,那么我们就需要为它分配一个空间,令rt = ++sz,将数组中下标为2的空间分配给了这一节点。
在这里插入图片描述
根据同样的道理,我们继续沿路径走,最终当l==r时,也就到达了最终的位置,此时可以对sum++,用来计数。
在这里插入图片描述
然后回溯,将路径上的点的数据进行更新。
在这里插入图片描述
用线段树来表示的话
在这里插入图片描述
我们只记录了路径上的点,后续如果要对其进行点的增加的话,我们也只是会沿一条路径走一遍,若为空,则将原数组上一个没有用过的点分配给它。

其实整体的思想有点像链式前向星,以数组来实现指针的操作。


三、求逆序对数

求逆序对数

无需离散化处理,直接存入,点的个数保证了我们是可以用数组存下所有路径,而与值域大小无关。

#include<bits/stdc++.h>
using namespace std;
int sz;
const int N=5e5;
const int maxn = 1e9;
struct node{
	int l,r,sum;
};
node t[N*32];
void up(int &rt,int l,int r,int x){
	if(!rt){
		rt=++sz;
		t[rt].l=t[rt].r=t[rt].sum=0;
	}
	if(l==r){
		t[rt].sum++;
		return;
	}
	int m=(l+r)/2;
	if(x<=m)up(t[rt].l,l,m,x);
	else up(t[rt].r,m+1,r,x);
	t[rt].sum=t[t[rt].l].sum+t[t[rt].r].sum;
}
long long q(int x,int y,int rt,int l,int r){
	if(!rt)return 0;
	if(x<=l&&r<=y)return t[rt].sum;
	int ans=0;
	int mid=(l+r)/2;
	if(x<=mid)ans+=q(x,y,t[rt].l,l,mid);
	if(y>mid)ans+=q(x,y,t[rt].r,mid+1,r);
	return ans;
}
int main(){
	sz=1;
	int n;
	int root=1;
	cin>>n;
	long long tt=0;
	for(int i=1;i<=n;i++){
		int tmp;
		cin>>tmp;
		tt+=q(tmp+1,maxn,1,1,maxn);
		up(root,1,maxn,tmp);	
	}
	cout<<tt<<endl;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值