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

樹狀陣列求逆序對

第一篇部落格就是樹狀陣列,已經過去半年了我樹狀陣列還是隻會個模板= =

CF1042D的題解一直看不懂,看到下面有人說和逆序對有關係,所以還是先補一下逆序對吧。

洛谷P1908是逆序對的模板題,資料很強,很好,就是題解質量參差不齊,很多人在資料加強後根本都是WA的,所以果然還是自己總結一下吧……

當給定的序列只有1-n並且沒有重複時,一種簡單的方法也就是大白上的方法就是直接用樹狀陣列維護,一開始將樹狀陣列初始化成0,然後從陣列第一項開始,每次add(a[i],1)。注意到query(a[i]-1)是所有在原陣列中在a[i]前面並且小於a[i]的數的個數*,所以所有在a[i]後面並且小於a[i]的數就是i-query(a[i]-1)(如果下標從1開始那麼就是i-1-query(a[i]-1)),也就是在樹狀陣列中a[i]前面那些為0的部分。

*因為在樹狀陣列中維護的是a[i]的大小,a[i]前面的數就是比a[i]小的數,而在這個時刻沒有被賦值成1的必然在原陣列中排在a[i]的後面,也就是逆序對了。每次計數的是以a[i]為首的逆序對,所以不會有重複。

還有另一種方法。我們把a[i]裝成一個結構體Node,其中val表示a[i]的大小,num表示在陣列中的位置(從1開始)。

```

struct Node{

int val, num; }

bool cmp(Node a, Node b){

    return a.val>b.val;

}

```

然後我們sort一下a也就是按val從大到小排序。和上面的方法有點相似的,初始化為0,每次add(a[i].num,1), ans+= query(a[i].num-1)。這裡樹狀陣列維護的是num,因為從大到小排序,所以這個時刻在樹狀數組裡如果為1說明大小比a[i]大

。所以query(a[i].num-1)就是原陣列中排在a[i]前面的並且比a[i]大的數的個數,這種方法裡每次計數的是是以a[i]為末尾的逆序對。

看起來第一種方法比第二種好很多,還省了一次排序。那麼如果不限制這些數字的範圍呢?比如n<=1e5,-1e9<=a[i]<=1e9?第二種方法仍然能正確地得出結果,而第一種好像就不能直接用了。

其實也是可以的,只要離散化一下。

因為這個問題中,答案與絕對大小無關而只與相對大小有關,比如-1e9,1e9,114514的逆序對數其實就是1,3,2的逆序對數。所以我們可以用上面的結構體把cmp裡的>換成<,sort一下a,然後i從1到n迴圈一次,賦值b[a[i].num]=i,然後b的範圍就是1-n,而b陣列的逆序對和a是相同的,這樣就可以用上面的第一種方法來求逆序對了。

說了這麼多,如果用上面的思路去做洛谷的P1908……還是會WA,因為裡面的資料允許重複。

上面說的兩種做法都沒有考慮過重複的情況。第一種方法裡,我們認為query(a[i]-1)是所有在原陣列中在a[i]前面並且小於a[i]的數的個數,而在離散化的時候,我們賦值b[a[i].num]=i,就可能讓原本在a裡一樣大的數在b裡被賦值成不一樣大,解決方法也很簡單,只要用一個tot來代替i就可以了,如果a[i].val!=a[i-1].val就=tot++,否則= b[a[i-1].num]。並且ans+= i-1-query(a[i])而不是上面的i-1-query(a[i]-1),否則之前已經填入的重複部分會被當做沒有填入計數。

其實這裡也可以用lower_bound來離散化,這樣就不需要a[i]是個結構體了。先讓把a的內容拷貝到b,然後排序b後a[i]=lower_bound(b+1,b+n+1,a[i])-b;

第二種做法的解決方案更簡單。我們認為query(a[i].num-1)是原陣列中排在a[i]前面的並且比a[i]大的數的個數,這裡出錯的唯一原因就是在計數時我們可能會將之前填入樹狀陣列的大小相同的數算進去。只要在從大到小排序的時候順便從編號大到小排序就不會把編號小的記進去了。

```

bool cmp(node a,node b)

{

    if(a.val!=b.val)return a.val>b.val;

    return a.num>b.num;

}

```