一、樹狀數組求逆序對的原理
1.問題描述:假設當前有一個數列a,求數列中逆序對數,即數字較小的數位置較數字較大的數靠后的有序對的個數。
那么有什么解法呢?
(1)O(N^2)暴力比對,TLE。
(2)歸並排序求逆序對(在此先不提);
(3)樹狀數組求逆序對。
2.樹狀數組求逆序對的原理
我們知道,樹狀數組是可以做到單點修改,區間求和的,那我們不妨以數字為下標,每來一個新的數就讓他對應數字為下標的數增加一,代表下標在當前已處理的數字中出現的次數;
方案一:那么我們將數列倒序做一遍樹狀數組,那么當前數字的前一個數的前綴和即為以該數為較大數的逆序對的個數。
因為我們是倒序處理的,每個數的前一個數的前綴和其實就是當前處理過的數中小於它的數的個數,也就是原數列中他后面的數字里小於它的個數,不必考慮會有多算或少算的情況發生。
代碼實現:
for(i=n;i>0;--i){
add(a[i]);
ans+=sum(a[i]-1);
}
方案二:正序做樹狀數組,那么當前下標減掉當前數字的前綴和即為以該數為較小數的逆序對個數。
因為是正序,那么對於每個當前的數,已加入的數字個數(算當前數)即為當前數字在數列中的下標,也就是樹狀數組中已經加入了這么多個數,那么他的前綴和代表小於它且在他前面的數的個數,用總數減掉前綴和即為以該數為較小數的逆序對個數,同樣,我們也不需要考慮多算或少算的情況發生。
代碼實現:
for(i=1;i<=n;++i){
add(a[i]);
ans+=i-sum(a[i]);
}
3.離散化的兩種方式
因為在做樹狀數組時我們要以數字為下標,所以我們要對數列中的數進行離散化。
方案一:copy出一個數組,sort一遍,以該數的地址為新數。
代碼實現:(b為copy出來的數列)
void discretize(){
sort(b+1,b+1+n);
unique(b+1,b+1+n)-b-1;
for(i=1;i<=n;++i) a[i]=lower_bound(b+1,b+1+n,a[i])-b;
}
方案二:對於每個數開結構體,一個記錄數字一個記錄當前序號,按照數字sort一遍,序號數列即為離散后的新數列,其證明大家可以自己出幾組數據比划比划就知道啦。
代碼實現:(cmp函數即為返回數字相比的結果)
void discretize(){
for(i=1;i<=n;++i){
a[i].num=rd();
a[i].d=i;
sort(a+1,a+1+n,cmp);
}
二、相關題目
1.[洛谷P1908]逆序對
Description
貓貓TOM和小老鼠JERRY最近又較量上了,但是畢竟都是成年人,他們已經不喜歡再玩那種你追我趕的游戲,現在他們喜歡玩統計。最近,TOM老貓查閱到一個人類稱之為“逆序對”的東西,這東西是這樣定義的:對於給定的一段正整數序列,逆序對就是序列中ai>aj且i<j的有序對。知道這概念后,他們就比賽誰先算出給定的一段正整數序列中逆序對的數目。
輸入格式:
第一行,一個數n,表示序列中有n個數。
第二行n個數,表示給定的序列。
輸出格式:給定序列中逆序對的數目。
Solution
裸的逆序對板子,直接套用即可。
Code
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[40010],b[40010],c[40010],n,m,i,j,k;
inline int rd(){
int x=0;
char c=getchar();
bool f=false;
while(!isdigit(c)){
if(c=='-') f=true;
c=getchar();
}
while(isdigit(c)){
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return f?-x:x;
}
void discretize(){
sort(b+1,b+1+n);
unique(b+1,b+1+n)-b-1;
for(i=1;i<=n;++i) a[i]=lower_bound(b+1,b+1+n,a[i])-b;
}
inline int lowbit(int x){return x&-x;}
int add(int x,int k){
for(int i=x;i<=n;i+=lowbit(i))c[i]+=k;
}
int sum(int x){
int ret=0;
for(int i=x;i>0;i-=lowbit(i)) ret+=c[i];
return ret;
}
int main(){
n=rd();
for(i=1;i<=n;++i) a[i]=b[i]=rd();
discretize();
long long ans=0;
for(i=n;i>0;--i){
add(a[i],1);
ans+=sum(a[i]-1);
}
printf("%lld\n",ans);
return 0;
}
