一、与普通线段树的比较
空间区别
之前学习的线段树空间大小为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;
}