[題解][筆記]lgP1908逆序對&權值線段樹
阿新 • • 發佈:2020-10-23
原題鏈
這個題其實完全不必用權值線段樹去做,只是現在有點看不懂樹狀陣列線段樹用途更多,而且我要學線段樹合併的原因
演算法概述:
權值線段樹其實和普通線段樹沒有什麼本質上的區別,只是維護的東西不一樣,平時我們做的線段樹是維護的區間和,而權值線段樹維護的是某個數或幾個數出現次數的和.同時應該注意的是權值線段樹的定義中的\(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; }