洛谷:逆序對P1908

博主也是想了很久,一直忘記考慮重複數據

我們採用樹狀數組來寫會更加方便…然而這種算法並沒有C++和Java的庫支持,需要自己手動實現。樹狀數組和線段樹很像,但能用樹狀數組解決的問題,基本上都能用線段樹解決,而線段樹能解決的樹狀數組不一定能解決。相比較而言,樹狀數組效率要高很多。

以下爲百度百科所解釋:

假設數組a[1..n],那麼查詢a[1]+...+a[n]的時間是log級別的,而且是一個在線的數據結構,支持隨時修改某個元素的值,複雜度也爲log級別。
來觀察這個圖:
令這棵樹的結點編號爲C1,C2...Cn。令每個結點的值爲這棵樹的值的總和,那麼容易發現:
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
...
C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16
這裏有一個有趣的性質:
設節點編號爲x,那麼這個節點管轄的區間爲2^k(其中k爲x二進制末尾0的個數)個元素。因爲這個區間最後一個元素必然爲Ax,
所以很明顯:Cn = A(n – 2^k + 1) + ... + An
算這個2^k有一個快捷的辦法,定義一個函數如下即可:

	int lowbit(int x){
		return x&(x^(x–1));
	}

利用機器補碼特性,也可以寫成:

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

當想要查詢一個SUM(n)(求a[n]的和),可以依據如下算法即可:
step1: 令sum = 0,轉第二步;
step2: 假如n <= 0,算法結束,返回sum值,否則sum = sum + Cn,轉第三步;
step3: 令n = n – lowbit(n),轉第二步。

可以看出,這個算法就是將這一個個區間的和全部加起來,爲什麼是效率是log(n)的呢?以下給出證明:
n = n – lowbit(n)這一步實際上等價於將n的二進制的最後一個1減去。而n的二進制裏最多有log(n)個1,所以查詢效率是log(n)的。
那麼修改呢,修改一個節點,必須修改其所有祖先,最壞情況下爲修改第一個元素,最多有log(n)的祖先。

所以修改算法如下(給某個結點i加上x):
step1: 當i > n時,算法結束,否則轉第二步;
step2: Ci = Ci + x, i = i + lowbit(i)轉第一步。
i = i +lowbit(i)這個過程實際上也只是一個把末尾1補爲0的過程。
對於數組求和來說樹狀數組簡直太快了!

注:
求lowbit(x)的建議公式:
lowbit(x):=x and -x;
或lowbit(x):=x and (x xor (x - 1));
lowbit(x)即爲2^k的值。

下面附上代碼:

#include <iostream>
#include <algorithm>
using namespace std;

int n, a[500005], f[500005],vis[500005],ans=0;
long long add=0;

struct Node {
    int sum, number,flag;
} node[500005];

bool cmp(Node a, Node b) {
    return a.sum < b.sum;
}

void add1(int x, int y) {
    while (x <= n) {
        a[x] += y;
        x += x & (-x);
    }
}

int add2(int x) {
    int add = 0;
    while (x) {
        add += a[x];
        x -= x & (-x);
    }
    return add;
}

int main() {
    cin>>n;
    for (int i = 1; i <= n; i++) {
        cin>>node[i].sum;
        node[i].number = i;
    }
    sort(node + 1, node + n + 1, cmp);
    for (int i = 1; i <= n; i++) {
        if(node[i].sum != node[i-1].sum)
            ans++;
        f[node[i].number] = ans;
    }
    for (int i = 1; i <= n; i++) {
        add1(f[i], 1);
        add += i - add2(f[i]);
    }
    cout<<add;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章