1. 程式人生 > >《挑戰程式設計競賽》3.3.1 資料結構-樹狀陣列 POJ3468 1804 1990 3109 2155 2886(4)

《挑戰程式設計競賽》3.3.1 資料結構-樹狀陣列 POJ3468 1804 1990 3109 2155 2886(4)

POJ3468 成段更新,區間求和

題意

給你N個數,Q個操作,操作有兩種,‘Q a b ’是詢問a~b這段數的和,‘C a b c’是把a~b這段數都加上c。

思路

此題是《挑戰》書中例題,線段樹和樹狀陣列都可以用,我的測試表明樹狀陣列效率略高一些。

主要思想是維護兩個樹狀陣列。根據要求提前推匯出對應於結果的對映關係。

另外線段樹的實現程式碼見我的部落格的3.3.2節。

程式碼(樹狀陣列)

Source Code

Problem: 3468       User: liangrx06
Memory: 2192K       Time: 1032MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 100000; typedef long long LL; int n; int a[N]; LL bit[2][N+1]; void add(int b, int i, int x) { while (i <= n) { bit[b][i] += x; i += i&-i; } } LL sum(int b, int
i) { LL s = 0; while (i > 0) { s += bit[b][i]; i -= i&-i; } return s; } LL f(int i) { return sum(0, i) + sum(1, i)*i; } int main(void) { int q; cin >> n >> q; memset(bit, 0, sizeof(bit)); for (int i = 0; i < n; i ++) { scanf
("%d", &a[i]); add(0, i+1, a[i]); } char c[2]; int l, r, x; while (q --) { scanf("%s", c); if (c[0] == 'Q') { scanf("%d%d", &l, &r); printf("%lld\n", f(r)-f(l-1)); } else { scanf("%d%d%d", &l, &r, &x); add(0, l, -x*(l-1)); add(1, l, x); add(0, r+1, x*r); add(1, r+1, -x); } } return 0; }

POJ1804 逆序數

題意

求快速排序的交換次數,其實就是求逆序數。

思路

逆序數目前已知最常見的方法是利用歸併排序來求。另外還可以用線段樹和樹狀陣列來求。都是O(nlog
n)的複雜度。
樹狀陣列的實現需要將原數範圍[-1000000, 1000000]對映到[1, 2000001]上,這樣才符合樹狀陣列的定義,並且不影響逆序數結果。
本人實現了歸併排序和樹狀陣列兩種方法。這裡分別給出程式碼。

程式碼1(樹狀陣列)

Source Code

Problem: 1804       User: liangrx06
Memory: 8064K       Time: 422MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1000;
const int ADD = 1000001;
const int M = ADD*2-1;

int n;
int a[N];

int bit[M+1];

int sum(int i)
{
    int s = 0;
    while (i > 0) {
        s += bit[i];
        i -= i&-i;
    }
    return s;
}

void add(int i, int x)
{
    while (i <= M) {
        bit[i] += x;
        i += i&-i;
    }
}

int main(void)
{
    int t;
    cin >> t;
    for (int k = 1; k <= t; k ++) {
        cin >> n;
        int ans = 0;
        memset(bit, 0, sizeof(bit));
        for (int i = 0; i < n; i ++) {
            scanf("%d", &a[i]);
            a[i] += ADD;
            ans += i - sum(a[i]);
            add(a[i], 1);
        }
        printf("Scenario #%d:\n%d\n\n", k, ans);
    }

    return 0;
}

程式碼2(歸併排序)

Source Code

Problem: 1804       User: liangrx06
Memory: 280K        Time: 32MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1000;

int n;
int a[N];

int merge_count(int l, int r)
{
    if (r == l) return 0;

    int res = 0;
    int m = (l + r) / 2;
    res += merge_count(l, m);
    res += merge_count(m+1, r);

    int i = l, j = m+1, k = l;
    int b[N];
    while (i <= m || j <= r) {
        if (i > m)
            b[k++] = a[j++];
        else if (j > r) {
            b[k++] = a[i++];
            res += (j-m-1);
        } else if (a[i] <= a[j]) {
            b[k++] = a[i++];
            res += (j-m-1);
        } else
            b[k++] = a[j++];
    }
    for (k = l; k <= r; k ++)
        a[k] = b[k];
    return res;
}

int main(void)
{
    int t;
    cin >> t;
    for (int k = 1; k <= t; k ++) {
        cin >> n;
        for (int i = 0; i < n; i ++)
            scanf("%d", &a[i]);
        printf("Scenario #%d:\n%d\n\n", k, merge_count(0, n-1));
    }

    return 0;
}

題意

思路

程式碼