1. 程式人生 > 實用技巧 >[題解][筆記]lgP1908逆序對&權值線段樹

[題解][筆記]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,cnt);//既然l,r是作為值域,那麼在離散化後就只有cnt個數了
	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;
}