G - 逆序對的數量

發表於2023-02-19

G - 逆序對的數量

原題連結

什麼是逆序對?

簡單來說,兩個數比較,下標小的數反而大,兩個數稱之為逆序對如\({3,1}\)就是這麼一個逆序對

歸併排序

由於逆序對逆序的性質,我們可以聯想到排序:
排序的過程就是消除逆序對的過程,消除的次數就是逆序對的數量

歸併排序的性質:每次劃分後合併時左右子區間都是從小到大排好序的,我們只需要統計右邊區間每一個數分別會與左邊區間產生多少逆序對即可

注意

逆序對的個數最大的情況發生在整個陣列逆序時即:

\[(n-1)+(n-2)+...+1 = \frac{n\cdot(n-1)}{2} \]

由於

\[n≤5×10^5 \]

答案是大於\(10^{10}\)的(會爆int)
注意要使用long long

程式碼1

點選檢視程式碼
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;

#define X first
#define Y second

typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 5e5+10;
const int M = 2e5+10;
int n,m;
int q[N],temp[N];

LL merge_sort(int l,int r){
	if(l >= r)return 0;

	int mid = l + r >> 1;
	LL res = merge_sort(l,mid) + merge_sort(mid+1,r);

	int k = 0,i = l,j = mid + 1;
	while(i <= mid && j <= r){
		if(q[i] <= q[j])temp[++k] = q[i++];
		else{
			temp[++k] = q[j++];
			res += mid - i +1;
		}
	}
	while(i <= mid)temp[++k] = q[i++];
	while(j <= r)temp[++k] = q[j++];

	for(int i = l,k = 1; i <= r; i ++,k ++)q[i] = temp[k];
	return res;
}

void solve(){
	cin >> n;
	for(int i = 1; i <= n; i ++ )cin >> q[i];
	cout << merge_sort(1,n) << nl;

}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);

	solve();
}

樹狀陣列

樹狀陣列相關知識以及模板題1
樹狀陣列相關知識以及模板題2

思路來源於暴力解法:從小到大列舉陣列的每一個數(天然地形成逆序對\(j<i\)(j指的是先前的數,i指的是當前的數)的條件),此時我們只需要知道前面有多少個數比a_i(當前數)小即可,這裡我們可以用到(就是一個陣列cnt)來記錄每個數的出現(怎麼記錄?每次列舉到這個數後,\(cnt[a_i]++\)即可)

然而,\(a_i\)的範圍是\(10^9\),而\(n\)的範圍卻只有\(5\cdot10^5\),直接開cnt陣列會mle

這時,我們又可以發現逆序對只跟相對大小有關係,所以我們的第一步最佳化便是將原陣列離散化處理(排序+去重)得到每個數的相對大小,同時每次列舉時使用二分來找到每個數的相對大小即可

然而,此時我們又遇到一個問題,對於每次統計前面有多少個數比a_i(當前數)小又需要多次求和,暴力解又會tle

這時,我們又可以聯想到多次求和有奇效的樹狀陣列,透過樹狀陣列來維護桶

答案顯而易見

\[\sum_{i = 1}^{n} \ ( \ i \ - \ getsum(1,k) \ ) \]

\(getsum(1,k)\)為小於等於\(a_i\)的數的數量(等於的話不是逆序對)
\(i - getsum(1,k)\)得到的則時關於\(a_i\)逆序對的數量

程式碼

點選檢視程式碼
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;

#define X first
#define Y second

typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 1e6+10;
const int M = 2e5+10;
int n,m;
int a[N],b[N];	//原陣列,樹狀陣列(維護桶)
vector<int> v;	//儲存所有待離散化的值進行排序和去重
LL ans = 0;

int lowbit(int x){
	return x & -x;
}

void add(int k,int x){
	while(k <= n){
		b[k] += x;
		k += lowbit(k);
	}
}

LL getsum(int l,int r){
	LL s1 = 0;
	l --;
	while(l){
		s1 += b[l];
		l -= lowbit(l);
	}
	LL s2 = 0;
	while(r){
		s2 += b[r];
		r -= lowbit(r);
	}
	return s2 - s1;
}

int find(int x){	//找到a[i]在序列中排第幾
	int l = 0,r = v.size() - 1;
	while(l < r){
		int mid = l + r >> 1;
		if(v[mid] >= x)r = mid;
		else l = mid + 1;
	}
	return l + 1;	//v從0開始
	//此處對映為1,2,3,...,n
}

void solve(){
	cin >> n;
	for(int i = 1; i <= n; i ++ ){
		cin >> a[i];
		v.push_back(a[i]);
	}

	sort(v.begin(),v.end());	//排序(從小到大)
	v.erase(unique(v.begin(),v.end()),v.end());	//去重
	// //離散化得到每個數的相對大小

	//列舉每個數找逆序對數量
	for(int i = 1; i <= n; i ++ ){
		int k = find(a[i]);
		add(k,1);	
		ans += (i - getsum(1,k));	//getsum(1,k)為小於等於a[i]的數的數量(等於的話不是逆序對)
	}
	cout << ans << nl;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);

	solve();
}

相關文章