[题解][笔记]lgP1908逆序对&权值线段树

原题链

这个题其实完全不必用权值线段树去做,只是现在有点看不懂树状数组线段树用途更多,而且我要学线段树合并的原因

算法概述:

权值线段树其实和普通线段树没有什么本质上的区别,只是维护的东西不一样,平时我们做的线段树是维护的区间和,而权值线段树维护的是某个数或几个数出现次数的和.同时应该注意的是权值线段树的定义中的\(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;
}
posted @ 2020-10-23 15:06  czyczy  阅读(81)  评论(0编辑  收藏  举报