計算冒泡排序的交換次數:
逆序數概念:在一個排列中,如果一對數的前后位置與大小順序相反,即前面的數大於后面的數,那么它們就稱為一個逆序
一個排列中所有逆序個數總和叫做這個排列的逆序數。 所以冒泡排序結束即是所有的逆序數為0
思路:
暴力:我們要計算逆序數,即使要統計出該值位置之前有多少個數比他大.我們用 arr[] 數組來表示序列值,則 Posx<Posy , 有Valuex>Valuey .那么可以得到 (O n2) 的方法

for(int Px = 1; Px<=n ; Px++){ for(int Py = Px-1; Py>0;Py--){ if(arr[Px]>arr[Py]) ans++; } }
我們在換個統計的思路,我們用一個 vis[] 數組來記錄,在前 i 個位置,有哪些值已經出現。這樣,比如當前位置為 3 值為4 ,在此之前已經有5和3被記錄,即原序列為 3 5 4 1 2,那么我們
只需要遍歷統計值大於 4 的位置之前有多少出現。上例中從4遍歷到5則有vis[5]=1,所以逆序就有一個.
而樹狀數組是一個用於維護前綴和的工具,所以我們維護一個vis[]從 當前值到最大值(最大位置)的一個前綴和(逆向前綴和),即可快速得出答案
當然由於逆向不太好寫,我們可以反向來統計,比如上例中 3 5 4 1 2,第一個位置3由於提早出現,之后的1和2會產生一個貢獻,所以我們統計該位置為之后位置產生的逆序數貢獻就可以正向求前綴和。
而之前則是統計當前位置之前出現的值對該位置的逆序貢獻。所以在后面代碼中貢獻值為 j - bit[i].
而維護前綴和的樹狀數組復雜度為 O( logn) ,所以將復雜度降為 O( nlogn)
#include <bits/stdc++.h> #define IOS ios::sync_with_stdio(0); cin.tie(0); #define mp make_pair #define Accept 0 using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> pii; const double Pi = acos(-1.0); const double esp = 1e-9; const int inf = 0x3f3f3f3f; const int maxn = 1e5+7; const int maxm = 1e6+7; const int mod = 1e9+7; const int MAXL = 1e6+7; int n; int bit[maxn]; int arr[maxn]; int vis[maxn]; //樹狀數組維護的前綴和
int sum(int i){ int s = 0; while(i>0){ s += bit[i]; i -= i & -i; } return s; } //單點更新 void add(int i,int x){ while(i<=n){ bit[i] += x; i += i & -i; } } void solve(){ int ans = 0; for(int j = 0;j<n;j++){ ans += j - sum(arr[j]); add(arr[j],1); }
//暴力的解法 // for(int i=0;i<n;i++){ // int cnt = 0; // for(int j=0;j<arr[i];j++){ // if(vis[j]) cnt++; // } // ans += i - cnt; // vis[arr[i]] =1; // } printf("%d\n",ans); } int main(){ memset(vis,0,sizeof(vis)); scanf("%d",&n); for(int i=0;i<n;i++){ scanf("%d",&arr[i]); } solve(); return 0; }