这个题其实完全不必用权值线段树去做,只是现在有点看不懂树状数组线段树用途更多,而且我要学线段树合并的原因
算法概述:
权值线段树其实和普通线段树没有什么本质上的区别,只是维护的东西不一样,平时我们做的线段树是维护的区间和,而权值线段树维护的是某个数或几个数出现次数的和.同时应该注意的是权值线段树的定义中的\(l,r\)跟平常普通的线段树表示的是区间的左右端点不同,它表示的是值域,也就是数值.
关于本题
这个题要求的是逆序对,先观察数据范围,发现数据范围很大,需要离散化,离散化其实也不难,就是重新映射到一个新的小的连续的整数序列上(看代码就知道了).还有很多细节都在代码里有注释,光靠口讲有点抽象,大家就凑合着看代码吧
代码
#include <bits/stdc++.h>
using namespace std;
long long n,a[1000010];
struct tmp{
long long val,id;
}b[1000010];
struct node{
long long l,r,sum;//权值线段树的下标l,r是值域而不是普通数组的下标
}tree[1000010 * 4];
bool cmp(tmp x,tmp y){
return x.val < y.val;
}
void build(long long num,long long l,long long r){
tree[num].l = l;tree[num].r = r;
if(l == r)return;
long long mid = (l + r) / 2;
build(num * 2,l,mid);build(num * 2 + 1,mid + 1,r);
}
long long ask(long long num,long long tar){
if(tree[num].l == tree[num].r)return tree[num].sum;
long long mid = (tree[num].l + tree[num].r) / 2;
if(tar <= mid)
return ask(num * 2,tar) + tree[num * 2 + 1].sum;
else return ask(num * 2 + 1,tar);
}
void change(long long num,long long tar){
if(tree[num].l == tree[num].r){
tree[num].sum++;
return;
}
long long mid = (tree[num].l + tree[num].r) / 2;
if(tar <= mid)
change(num * 2,tar);
else change(num * 2 + 1,tar);
tree[num].sum = tree[num * 2].sum + tree[num * 2 + 1].sum;
}
int main(){
scanf("%lld",&n);
for(long long i = 1;i <= n;i++){
scanf("%lld",&a[i]);
b[i].val = a[i]; //离散化
b[i].id = i;
}
sort(b + 1,b + n + 1,cmp);
long long cnt = 0;
for(long long i = 1;i <= n;i++){
if(b[i].val != b[i - 1].val || i == 1)cnt++;
a[b[i].id] = cnt;
}//离散化结束,现在的数列是一个从1开始的连续的数列
build(1,1,n);
long long ans = 0;
for(long long i = 1;i <= n;i++){
ans += ask(1,a[i] + 1);//权值线段树计算的是大于等于x的数的个数
//而题目中要求严格大于,所以要加1.
//因为离散化了所以保证了数据是连续的,+1后一定会是下一个数
change(1,a[i]);//出现了一次,就加一个sum
}
printf("%lld\n",ans);
return 0;
}