【樹狀陣列 求逆序對】排序
首先需要了解逆序對是什麼:
逆序對就是如果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;
}