TIOJ 1080

TIOJ 1080

題敘

https://tioj.ck.tp.edu.tw/problems/1080
逆敘數對模板題
對於一個數列 $S$ 若 $S$ 的第 $i$ 項 $s_i$ 與第 $j$ 項 $s_j$ 符合 $s_i>s_j$ ,並且 $i<j$ ,那麼我們說 $(i,j)$ 是一個逆序數對。請問給定 $S$ ,總共有多少個逆序數對

想法

對於一個元素來說,他的逆敘數對數量會跟在這個元素之後比該元素還小的數量相同
也就是說,我們的目的是要每個元素找到符合這樣條件的數量總和

如果今天我們拿到的是一個已經排序好的序列,保證元素是嚴格遞增
那麼我們可以很快的知道每個元素的逆敘述對有多少個

元素 1 2 3 4 5
逆敘數對量 4 3 2 1 0

換個角度來想,也就是我們希望能做出一個具有規律性的序列,如此一來就可以很快速的解出每個元素的答案
可以利用 merge sort 來處理這樣的問題
在排序的過程當中,我們會不斷地將序列分成兩個部分
而當左邊的元素 $i$ 大於右邊的元素 $j$ 時,我們可以很明顯的知道
與 $i$ 以及 $i$ 後面屬於左邊的元素和 $j$ 必定會是逆敘數對
因此每次遇到這種情況就加上還在左邊的元素數量

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//By Koios1143
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define int long long
#define pii pair<int,int>
using namespace std;
int n,Case=1,arr[2000100],ans=0,tmp[2000100];
void merge(int L,int R){
int mid=(L+R)>>1;
for(int i=L,j=mid+1,k=L ; k<=R ; k++){
if(i>mid)
tmp[k]=arr[j++];
else if(j>R)
tmp[k]=arr[i++];
else if(arr[i]<=arr[j])
tmp[k]=arr[i++];
else{
ans+=(mid-i+1);
tmp[k]=arr[j++];
}
}
for(int i=L ; i<=R ; i++)
arr[i]=tmp[i];
}
void merge_sort(int L,int R){
int mid=(L+R)>>1;
if(L==R)
return;
merge_sort(L,mid);
merge_sort(mid+1,R);
merge(L,R);
}
signed main(){
IOS;
while(cin>>n && n){
ans=0;
for(int i=0 ; i<n ; i++){
cin>>arr[i];
}
merge_sort(0,n-1);
cout<<"Case #"<<Case++<<": "<<ans<<'\n';
}
return 0;
}

複雜度

與 merge sort 相同 $O(NlogN)$