1. 程式人生 > >【樹狀陣列 求逆序對】排序

【樹狀陣列 求逆序對】排序

首先需要了解逆序對是什麼

逆序對就是如果i > j && a[i] < a[j],這兩個就算一對逆序對。其實也就是對於每個數而言,找找排在其前面有多少個比自己大的數

那麼思路就來了,樹狀陣列又一次地優化了這種“需要遍歷”的情況。那不就很容易了嗎?依次把序列裡的數放到樹狀陣列中的A[i]上去(實際是以C[i]形式的插入函式),注意A[i]是以數值大小從小到大排列的。先插入的說明排在序列的前面,那麼後插入的就可以看看之前插入的比你大的數有多少,即i-sum(i),其實也就是看序列前面比你大的數有多少個,即找逆序對。

通過樹狀陣列找逆序對的原理(圖解,我看了就懂了):

https://blog.csdn.net/ssimple_y/article/details/53744096


瞭解技巧“離散化”

這個技巧很有侷限性(至少以我目前的認知來說),幾乎只適合在樹狀陣列求逆序對來結合使用的。

什麼時候要用這個技巧呢?根據以上說的原理,我們知道需要以“數值大小”作為A[i]標準來升序排,所以需要給C陣列開元素最大可能值的記憶體。但萬一輸入的資料很大,那麼C陣列豈不是要開很大?記憶體超限!!而如果要用離散化,只需要給C陣列開元素數量的記憶體。

建立一個結構體包含val和id, val就是輸入的數,id表示輸入的順序。然後按照val從小到大排序,如果val相等,那麼就按照id排序。

如果沒有逆序的話,肯定id是跟i(表示拍好後的順序)一直一樣的,如果有逆序數,那麼有的i和id是不一樣的。所以,利用樹狀陣列的特性,我們可以簡單的算出逆序數的個數。

如果還是不明白的話舉個例子。(輸入4個數)

輸入:9 -1 18 5

輸出 3.

輸入之後對應的結構體就會變成這樣

val:9 -1 18 5

id:  1  2  3  4

排好序之後就變成了

val :  -1 5 9 18

id:      2 4  1  3

2 4 1 3 的逆序數 也是3

之後再利用樹狀陣列的特性就可以解決問題了

我覺得很神奇,直接得到“離散化”的結論把原序列中每個元素的值和下標存到一個結構體node裡去,之後把node陣列按元素值大小從小到大排序(注意結構體裡的過載<運算子的寫法 不是男左女右了我結論有誤T T 反正考場上試一試即可),這樣得到的結點的下標值即是離散化結果,等效於原序列的數值。把這些下標值當成原序列,按照樹狀陣列求逆序對的原理做。

習題:排序


大致思路


這個就是逆序對的定義呀!求逆序對的模板題。

這道題如果不離散化,C開1e9記憶體,超限,所以離散化,C只需要開5e5記憶體。

AC程式碼

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn=500001;
int c[maxn];
struct Node
{
	int v,index;
	bool operator < (const Node &b) const
	{
		return v<b.v; //從小到大排序 
	}
}node[maxn];
int n;
void add(int i)
{
	while(i<=n)
	{
		c[i]++;
		i+=i&(-i);	
	}
}
long long sum(int i)
{
	long long res=0;
	while(i>0)
	{
		res+=c[i];
		i-=i&(-i);
	}
	return res;
}

int main()
{
	cin>>n;
	int a;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		node[i].index=i;
		node[i].v=a;
	}
	sort(node+1,node+1+n);
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		add(node[i].index);  //離散化結果—— 下標等效於數值
		ans+=i-sum(node[i].index); //得到之前有多少個比你大的數(逆序對)
	}
	cout<<ans;
	return 0;
}