線段樹入門

線段樹入門

  • 瞭解線段樹

    線段樹是一種二叉搜索樹,它將一個區間劃分成一些較小的區間,最終劃分成單元區間,每個單元區間對應線段樹中的一個葉結點,表示線段上一個點。如下圖。
    線段樹

  • 線段樹的優勢
    首先,先知道在線段樹上進行的操作複雜度是 O(log2n)

    現在,用最樸樸素的方式,在一個一維數組 a[n] 中,我們要修改某一個點的值時,比如將原數組中 a[3]=3 改成 a[3]=6 ,只需要 a[3]=6; 即可,複雜度爲O(1);若需要求 a[k] (0≤i≤k<j<n) 的和時,需就需要遍歷數組,複雜度爲O(n)。

    雖然從單次操作看來線段樹沒有明顯的優勢,但是,當我們需要進行多次修改和查詢操作時,再使用樸素方法時就是 O(n²) 的複雜度,由於操作數次數很多,這種方法在競賽中是完全不能接受的。 而使用線段樹時,就是 O(nlog2n) 的複雜度。所以,再多次查詢和多次修改的操作中,線段樹是一個很有優勢的數據結構。

    通過線段樹,就可以在 O(log2n) 的時間複雜度下完成區間查詢、區間修改、單點查詢、單點修改、區間最值等操作。

    接下來以區間求和介紹線段樹的思路和寫法。

  • 建樹

    #include<bits/stdc++.h>
    #define lson l, mid, rt<<1  //左子樹
    #define rson mid+1, r, rt<<1|1  //右子樹
    using namespace std;
    const int MAX = 100;  //數據量
    int sum[MAX<<2];  //採用順序存儲結構建樹,注意建樹所用空間是4*MAX
    
    void push_up(int rt){  //向上更新
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];  //根結點的值等於左右子樹區間的值的和
    }
    
    void build(int l, int r, int rt){  //l,r是區間範圍,rt是根結點下標
        if(l==r){  //當l==r時,就是葉節點了,相當於線段上的某一點
            sum[rt] = 1;  //初始化,根據需要決定
            return ;
        }
        int mid = (l+r)>>1;
        build(lson);  //遞歸創建左子樹
        build(rson);  //遞歸創建右子樹
        push_up(rt);
    }
    
  • 單點查詢

    //單點查詢
    int query(int x, int l, int r, int rt){  //x是要查詢的點,l,r是線段區間,rt是樹的根節點
        if(l==r && l==x){  //達到葉結點,並且與要查詢的點吻合
            return sum[rt];
        }
        int mid = (l+r)>>1;
        if(x<=mid) query(x, lson);  //查詢的點在左區間
        else query(x, rson);  //查詢的點在右區間
    }
    
  • 區間查詢

    //區間查詢
    int query(int b, int e, int l, int r, int rt){  //b,e是求和區間,l,r是線段區間,rt是樹的根節點
        if(b<=l && e>=r){  //當前區間完全包含在所求區間內
            return sum[rt]; //返回該結點的值,就是該結點覆蓋線段上區間[l,r]的點的值的和
        }
        int mid = (l+r)>>1, ans = 0;
        /*
        這裏理解一下,求區間和的時候分成三種情況,所求區間:
        1.完全包含在左子樹包含的區間[l, mid]內;
        2.完全包含在右子樹包含的區間[mid+1, r]內;
        3.在左子樹區間有一部分,右子樹的區間有一部分,就要將所求區間分成兩部分求和
        */
        if(l<=mid) ans += query(b, e, lson);
        if(e>=mid+1) ans += query(b, e, rson);
        return ans;
    }
    
    //區間查詢第二種容易理解的方式
    int query(int b, int e, int l, int r, int rt){  //b,e是求和區間,l,r是線段區間,rt是樹的根節點
        if(b==l && e==r){  //當前區間與所求區間吻合
            return sum[rt]; //返回該結點的值,就是該結點覆蓋線段上區間[l,r]的點的值的和
        }
        int mid = (l+r)>>1, ans = 0;
        //這裏依舊分成三種情況
        if(e<=mid) ans += query(b, e, lson);  //完全包含在左子樹包含的區間[l, mid]內
        else if(l>=mid+1) ans += query(b, e, rson);  //完全包含在右子樹包含的區間[mid+1, r]內
        else ans = query(b, mid, lson) + query(mid+1, e, rson);  //在左子樹區間有一部分,右子樹的區間有一部分
        return ans;
    }
    
  • 單點更新

    //單點更新
    void update(int x, int l, int r, int rt){  //x需要更新的點的位置
        if(l==r && l==x){
            sum[rt] = newdata;  //更新值
            return ;
        }
        int mid = (l+r)>>1;
        if(x<=mid) update(x, lson);  //需要更新的點在左子樹
        else update(x, rson);  //需要更新的點在右子樹
    }
    
  • 區間更新
    對於更新區間[l, r]內的值(區間內每一個點同時加上某一個數),如果使用普通的數組,只需要遍歷一邊即可,時間複雜度是 O(n) ,同樣,如果使用線段樹的結構,對區間內每個點的值進行更新時,需要對每個數(O(n))先進行查找(O(log2n))再更改,時間複雜度爲O(nlog2n),反而高於普通方法,所以在線段樹上進行區間更新時,並不是採用直接更新某些點的方式,而是採用更新區間並標記,在需要時進行標記下放的方式完成的。稱爲lazy-tag方法。

    lazy-tag方法當修改的是一整個區間時,只對這個線段區間進行整體上的修改,其內部每個元素的內容先不做修改,只有當這部分線段的一致性被破壞時,才把變化的值傳遞給子區間。那麼每次區間修改的時間複雜度就是O(log2n),N次區間修改操作的時間複雜度就是O(nlog2n)。

    下面用圖解的形式來說明。與前面保持一致,使用 sum[MAX] 存放樹,新增一個 add[MAX] 用來保存相應節點的 lazy 值。
    初始狀態:
    初始狀態
    首先將區間 [1, 3] 加上 1
    a.[1,3] 分成 [1, 2] 和 [3,3]兩個區間
    b.更新兩個區間的和以及對應的 lazy 值 在這裏插入圖片描述再將區間 [2,4] 加 1
    a.區間 [2,4] 分爲 [2,2] 和 [3,4] 兩個區間。
    b.更新 [2, 2] 時,經過 [1,2] ,[1,2] 區間的 lazy 值不爲零,說明該區間的點更新過,需要先將 lazy 標記下放到兩個子區間,然後標記清零。再繼續深入。
    c.[3,4] 恰好爲需要修改的區間,直接更新區間和,並將 lazy 標記。
    在這裏插入圖片描述

    //標記下放
    void push_down(int rt, int n){  //n是區間長度
        add[rt<<1] += add[rt];  //更新子區間lazy
        add[rt<<1|1] += add[rt];
        sum[rt<<1] += (n-(n>>1))*add[rt];  //更新子區間
        sum[rt<<1] += (n>>1)*add[rt];
        add[rt] = 0;  //取消本層lazy
    }
    //區間更新
    void update(int b, int e, int x, int l, int r, int rt){
        if(b<=l && e>=r){
            add[rt] += x;  //lazy增加
            sum[rt] += (r-l+1)*x;  //區間和增加
            return ;
        }
        push_down(rt, (r-l+1));  //lazy下放
        int mid = (l+r)>>1;
        if(b<=mid) update(b, e, x, lson);  //更新左區間
        if(e>=mid+1) update(b, e, x, rson);  //更新右區間
        push_up(rt);
    }
    
  • 離散化
    當節點規模很大時,由於空間限制,很難在程序中建立二叉樹時,就需要用到離散化,離散化實際上就是將大的二叉樹壓縮成較小的二叉樹,但是壓縮前後子區間的關係不變。

    例如一塊宣傳欄,橫向長度刻度爲1→10,貼四張不同顏色的海報,他們和宣傳欄等寬,長度分別爲[1,3] 、[2,5]、[3,8]、[3,10] ,並且用後者覆蓋前者,問最後能見幾種顏色的海報。

    離散化步驟如下:
    (1)提取四張海報的八個頂點 1, 3, 2, 5, 3, 8, 3, 10
    (2)排序並刪除相同的端點 1, 2, 3, 5, 8, 10
    (3)把原線段上的端點映射到新線段上:
    在這裏插入圖片描述
    新的 4 張海報爲[1,3]、 [2,4]、 [3,5]、 [3,6] ,覆蓋關係不變,新的宣傳欄長度壓縮到 6 。

練習

Lost Cows
A Simple Problem with Integers
敵兵佈陣
Minimum Inversion Number
Just a Hook
I Hate It
Billboard
Ultra-QuickSort
Buy Tickets
Stars
Who Gets the Most Candies?

  • 題意:給出2->n頭牛前面有多少頭比他編號少的數目,求出每頭牛原來的編號,

  • 思路:從後面向前遍歷,對第m頭牛前面有a頭牛,表示此牛在剩餘的牛中排名a+1.用線段樹維護區間裏面未知編號的牛的數量,找到滿足條件的區間,區間右端點即爲此牛的編號。找到後將此牛標記一下已知編號。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    using namespace std;
    int sum[17000];
    int ans[8005], que[8005];
    int q, ran;
    
    void build(int n, int l, int r){
        if(l==r){
            sum[n] = 1;
            return ;
        }
        int m = (l+r)>>1;
        build(2*n, l, m);
        build(2*n+1, m+1, r);
        sum[n] = sum[2*n] + sum[2*n+1];
    }
    
    int query(int n, int l, int r){
        if(l==r) return r;
        int m = (l+r)>>1;
        if(ran<=sum[2*n]){
            return query(2*n, l, m);
        }
        else{
            ran -= sum[2*n];
            return query(2*n+1, m+1, r);
        }
    }
    
    void update(int n, int l, int r){
        if(l==r){
            sum[n]--;
            return ;
        }
        int m = (l+r)>>1;
        if(ran>m) update(2*n+1, m+1, r);
        else update(2*n, l, m);
        sum[n] = sum[2*n] + sum[2*n+1];
    }
    
    int main(){
        scanf("%d", &q);
        for(int i=1; i<q; i++) scanf("%d", &que[i]);
        que[0] = 0;
        build(1, 1, q);
        for(int i=q-1; i>=0; i--){
            ran = que[i]+1;
            ans[i] = query(1, 1, q);
            ran = ans[i];
            update(1, 1, q);
        }
        for(int i=0; i<q; i++) printf("%d\n", ans[i]);
        return 0;
    }
    
  • 模板題,就是區間查詢和區間修改。區間修改時,直接修改點必然會超時,這就用到了區間修改的 lazy 標記。還有就是注意一下數據範圍,使用 long long 。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    #define ll long long
    using namespace std;
    
    const int MAXN = 1e5+10;
    ll sum[MAXN<<2]={0}, add[MAXN<<2]={0};
    
    void push_up(int rt){
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    
    void push_down(int rt, int m){
        if(add[rt]){
            add[rt<<1] += add[rt];
            add[rt<<1|1] += add[rt];
            sum[rt<<1] += (m-(m>>1))*add[rt];
            sum[rt<<1|1] += (m>>1)*add[rt];
            add[rt] = 0;
        }
    }
    
    void build(int l, int r, int rt){
        add[rt] = 0;
        if(l == r){
            scanf("%lld", &sum[rt]);
            return ;
        }
        int mid = (l+r)>>1;
        build(lson);
        build(rson);
        push_up(rt);
    }
    
    void update(int a, int b, ll c, int l, int r, int rt){
        if(a<=l && b>=r){
            sum[rt] += (r-l+1)*c;
            add[rt] += c;
            return ;
        }
        push_down(rt, r-l+1);
        int mid = (l+r)>>1;
        if(a<=mid) update(a, b, c, lson);
        if(b>mid) update(a, b, c, rson);
        push_up(rt);
    }
    
    ll query(int a, int b, int l, int r, int rt){
        if(a<=l && b>=r) return sum[rt];
        push_down(rt, r-l+1);
        int mid = (l+r)>>1;
        ll ans = 0;
        if(a<=mid) ans += query(a, b, lson);
        if(b>mid) ans += query(a, b, rson);
        return ans;
    }
    
    int main(){
        int n, m;
        scanf("%d%d", &n, &m);
        build(1, n, 1);
        while(m--){
            char str[2];
            int a, b;
            ll c;
            scanf("%s", str);
            if(str[0] == 'C'){
                scanf("%d%d%lld", &a, &b, &c);
                update(a, b, c, 1, n, 1);
            }
            else{
                scanf("%d%d", &a, &b);
                printf("%lld\n", query(a, b, 1, n, 1));
            }
        }
        return 0;
    }
    
  • 思路:單點更新和區間求和。模板題

  • 代碼

    #include<iostream>
    #include<stdio.h>
    using namespace std;
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    
    const int MAXN = 50005;
    int sum[MAXN<<2]={0};
    //建樹
    void build(int l, int r, int rt){
        if(l == r){
            scanf("%d", &sum[rt]);
            return ;
        }
        int mid = (l+r)>>1;
        build(lson);
        build(rson);
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    //單點修改
    void update(int i, int j, int l, int r, int rt){
        if(l==r){
            sum[rt] += j;
            return ;
        }
        int mid = (l+r)>>1;
        if(i<=mid) update(i, j, lson);
        else update(i, j, rson);
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    //區間查詢
    int query(int i, int j, int l, int r, int rt){
        if(i==l && j==r) return sum[rt];
        int mid = (l+r)>>1;
        if(j>=mid+1 && i<=mid) return query(i, mid, lson)+query(mid+1, j, rson);
        else if(j<=mid) return query(i, j, lson);
        else /*(i>=mid+1)*/ return query(i, j, rson);
    }
    
    int main(){
        int m, n, i, j;
        string str;
        scanf("%d", &m);
        for(int k=1; k<=m; k++){
            printf("Case %d:\n", k);
            scanf("%d", &n);
            build(1, n, 1);
            while(1){
                cin>> str;
                if(str=="End") break;
                scanf("%d%d", &i, &j);
                if(str == "Query"){
                    printf("%d\n", query(i, j, 1, n, 1));
                }
                else{
                    if(str=="Add") update(i, j, 1, n, 1);
                    else update(i, 0-j, 1, n, 1);
                }
            }
        }
        return 0;
    }
    
  • 給你一個數列,求這個數列的逆序數。這是一道求逆序數的模板題。
    那麼如何通過線段樹求逆序數呢。首先我們知道一個數的逆序數的時候,就看在它的前面出現了幾個比他大的數,那麼這個數就是它的逆序數。所以,依照輸入順序,輸入一個數後a後,就查找區間 [a+1, n] 中有幾個數已經存在了,就是 a 的逆序數,然後標記 a 。找完所有數的逆序數再相加就可以了。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    
    const int MAXN = 5005;
    int sum[MAXN<<2]={0}, pre[MAXN], ma, ans;
    //建樹
    void build(int l, int r, int rt){
        if(l == r) return ;
        int mid = (l+r)>>1;
        build(lson);
        build(rson);
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    //單點修改
    void update(int x, int l, int r, int rt){
    //    cout<< x<< "--"<< l<< "--"<< r<< endl;
        if(l==r && l==x){
            sum[rt] = 1;
            return ;
        }
        int mid = (l+r)>>1;
        if(x<=mid) update(x, lson);
        else update(x, rson);
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    //區間查詢
    int query(int i, int j, int l, int r, int rt){
        if(i>j) return 0;
        if(i==l && j==r) return sum[rt];
        int mid = (l+r)>>1;
        if(j>=mid+1 && i<=mid) return query(i, mid, lson)+query(mid+1, j, rson);
        else if(j<=mid) return query(i, j, lson);
        else return query(i, j, rson);
    }
    
    int main(){
        int n;
        while(~scanf("%d", &n)){
            memset(sum, 0, sizeof(sum));
            ma = 0;
            build(1, n, 1);
            for(int i=0; i<n; i++){
                scanf("%d", &pre[i]);
                update(pre[i]+1, 1, n, 1);
                ma += query(pre[i]+2, n, 1, n, 1);
            }
            ans = ma;
            for(int i=0; i<n; i++){
                ma += (n-pre[i]-1)-pre[i];
                if(ma<ans) ans = ma;
            }
            printf("%d\n", ans);
        }
        return 0;
    }
    
  • 模板題區間修改。但這道題中的區間修改與上面講到的區間修改不同,此題中的區間修改式改變區間中的值爲給定值,不是累加關係。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    #define ll int
    using namespace std;
    
    const int MAXN = 1e5+10;
    ll sum[MAXN<<2]={0}, add[MAXN<<2]={0};
    
    void push_up(int rt){
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    
    void push_down(int rt, int m){
        if(add[rt]){
            add[rt<<1] = add[rt];
            add[rt<<1|1] = add[rt];
            sum[rt<<1] = (m-(m>>1))*add[rt];
            sum[rt<<1|1] = (m>>1)*add[rt];
            add[rt] = 0;
        }
    }
    
    void build(int l, int r, int rt){
        add[rt] = 0;
        if(l == r){
            sum[rt] = 1;
            return ;
        }
        int mid = (l+r)>>1;
        build(lson);
        build(rson);
        push_up(rt);
    }
    
    void update(int a, int b, ll c, int l, int r, int rt){
        if(a<=l && b>=r){
            sum[rt] = (r-l+1)*c;
            add[rt] = c;
            return ;
        }
        push_down(rt, r-l+1);
        int mid = (l+r)>>1;
        if(a<=mid) update(a, b, c, lson);
        if(b>mid) update(a, b, c, rson);
        push_up(rt);
    }
    
    int main(){
        int k;
        scanf("%d", &k);
        for(int i=1; i<=k; i++){
            int N, Q, x, y, z;
            scanf("%d%d", &N, &Q);
            build(1, N, 1);
            while(Q--){
                scanf("%d%d%d", &x, &y, &z);
                update(x, y, z, 1, N, 1);
            }
            printf("Case %d: The total value of the hook is %d.\n", i, sum[1]);
            memset(sum, 0, sizeof(sum));
            memset(add, 0, sizeof(add));
        }
        return 0;
    }
    
  • 模板題,單點修改+區間最值。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    #define ll int
    using namespace std;
    
    const int MAXN = 2e5+10;
    ll sum[MAXN<<2]={0};
    
    void push_up(int rt){
        sum[rt] = sum[rt<<1]>sum[rt<<1|1]? sum[rt<<1]:sum[rt<<1|1];
    }
    
    void build(int l, int r, int rt){
        if(l == r){
            scanf("%d", &sum[rt]);
            return ;
        }
        int mid = (l+r)>>1;
        build(lson);
        build(rson);
        push_up(rt);
    }
    
    void update(int a, int b, int l, int r, int rt){
        if(l==r){
            sum[rt] = b;
            return ;
        }
        int mid = (l+r)>>1;
        if(a<=mid) update(a, b, lson);
        else update(a, b, rson);
        push_up(rt);
    }
    
    int query(int a, int b, int l, int r, int rt){
        if(a<=l && b>=r) return sum[rt];
        int mid = (l+r)>>1;
        int x=0, y=0;
        if(a<=mid) x = query(a, b, lson);
        if(b>=mid+1) y = query(a, b, rson);
        return x>y? x:y;
    }
    
    int main(){
        int n, m, a, b;
        char c;
        while(~scanf("%d%d", &n, &m)){
            build(1, n, 1);
            while(m--){
                scanf(" %c%d%d", &c, &a, &b);
            if(c=='Q') printf("%d\n", query(a, b, 1, n, 1));
            else update(a, b, 1, n, 1);
            }
        }
        return 0;
    }
    
  • 單點修改+區間查詢(查找到某一點)的模板題。這道題難點在如何使用線段樹,方法就是:由於海報的高都是一個單位,所以可以將海報板按單位長度分成一行一行,線段樹的每個單元區間(就是線段樹的葉節點,也是線段上的一個點)代表一行,單元區間的值表示這個行剩下的長度。因爲貼海報的時候是依據上和左優先,其次是下和右的原則,所以只需要從線段上從走向有查找,就相當於從海報板上從上向下查找,對於一行,看是否可以貼(剩餘長度是否夠)就可以,實際上是不用考慮從左向右的。所以,想明白之後,就是單點修改+區間查詢的模板題。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    #define ll int
    using namespace std;
    
    const int MAXN = 2e5+5;
    ll sum[MAXN<<2];
    int h, w, n, pos, x;
    
    void push_up(int rt){
        sum[rt] = sum[rt<<1]>sum[rt<<1|1]? sum[rt<<1]:sum[rt<<1|1];  //保存區間最大值,當該區間最大值比海報長度小時,就肯定不貼在該區間,也就不用搜索了。
    }
    
    void build(int l, int r, int rt){
        if(l == r){
            sum[rt] = w;
            return ;
        }
        int mid = (l+r)>>1;
        build(lson);
        build(rson);
        push_up(rt);
    }
    
    void update(int l, int r, int rt){
        if(l==r){
            sum[rt] -= x;
            return ;
        }
        int mid = (l+r)>>1;
        if(pos<=mid) update(lson);
        else update(rson);
        push_up(rt);
    }
    
    bool query(int l, int r, int rt){
        if(l==r){
            if(x<=sum[rt]){
                pos = l;
                return true;
            }
            else return false;
        }
        int mid = (l+r)>>1;
        if(x<=sum[rt<<1]) return query(lson);
        else return query(rson);
    }
    
    int main(){
        while(~scanf("%d%d%d", &h, &w, &n)){
            h = min(h, n);
            build(1, h, 1);
            while(n--){
                scanf("%d", &x);
                if(query(1, h, 1)){
                    printf("%d\n", pos);
                    update(1, h, 1);
                }
                else printf("-1\n");
            }
        }
        return 0;
    }
    
  • 逆序數+離散化。這道題的突破點在於求逆序數。因爲排序時是通過相鄰元素兩兩交換完成的,所以實際上排序完成後的交換次數就是給定初始序列的逆序數。實際上就是通過線段樹求逆序數的問題了。另外就是離散化的問題。由於要排序的數據可能很大,如果只根據數據建立線段樹是行不通的,可以嘗試一下,這就還需要先進行離散化,再求逆序數。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    #include<algorithm>
    #include<string.h>
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    using namespace std;
    const int MAX = 5e5+5;
    int sum[MAX<<2];
    int n;
    __int64 num;
    typedef struct{
        int key1, key2;
        int value;
    }node;
    node in[MAX];
    
    bool cmp1(node n1, node n2){
        return n1.value<n2.value;
    }
    
    bool cmp2(node n1, node n2){
        return n1.key1<n2.key1;
    }
    
    
    void push_up(int rt){
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    
    void update(int x, int l, int r, int rt){
        if(l==r){
            sum[rt]=1;
            return ;
        }
        int mid = (l+r)>>1;
        if(x<=mid) update(x, lson);
        else update(x, rson);
        push_up(rt);
    }
    
    int query(int b, int e, int l, int r, int rt){
        if(b>e) return 0;
        if(b<=l && e>=r) return sum[rt];
        int mid = (l+r)>>1;
        int ans = 0;
        if(b<=mid) ans += query(b, e, lson);
        if(e>=mid+1) ans += query(b, e, rson);
        push_up(rt);
        return ans;
    }
    
    int main(){
        while(1){
            scanf("%d", &n);
            if(n==0) break;
            memset(sum, 0, sizeof(sum));
            num = 0;
            for(int i=1; i<=n; i++){
                in[i].key1 = i;
                scanf("%d", &in[i].value);
            }
            stable_sort(in+1, in+n+1, cmp1);
            for(int i=1; i<=n; i++) in[i].key2 = i;
            stable_sort(in+1, in+n+1, cmp2);
    
            for(int i=1; i<=n; i++){
                num += query(in[i].key2+1, n, 1, n, 1);
                update(in[i].key2, 1, n, 1);
            }
            printf("%I64d\n", num);
        }
        return 0;
    }
    
  • 買票插隊,每行兩個數 a 和 b ,表示編號爲 b 的人插在了隊伍裏第 a 個人後面,問最後的隊列順序(按順序輸出每個人的編號)。這道題和奶牛排序的問題類似,由於最後一個人插進隊伍之後,這個人在隊伍裏的位置就確定了,就可以不考慮這個人對其他人的位置的影響了,所以,採用從後向前遍歷的方法來確定某一個人的位置,使用線段樹維護區間和表示該區間有多少個人沒確定位置就可以了。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    #include<algorithm>
    #include<string.h>
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    using namespace std;
    const int MAX = 2e5+5;
    int sum[MAX<<2];
    int n, p[MAX], v[MAX], ans[MAX], i;
    
    void push_up(int rt){
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    
    void build(int l, int r, int rt){
        if(l==r){
            sum[rt] = 1;
            return ;
        }
        int mid = (l+r)>>1;
        build(lson);
        build(rson);
        push_up(rt);
    }
    
    void query(int x, int l, int r, int rt){
        if(l==r){
            sum[rt] = 0;
            ans[l] = v[i];
            return ;
        }
        int mid = (l+r)>>1;
        if(x<=sum[rt<<1]) query(x, lson);
        else query(x-sum[rt<<1], rson);
        push_up(rt);
    }
    
    int main(){
        while(~scanf("%d", &n)){
            build(1, n, 1);
            for(int i=0; i<n; i++) scanf("%d%d", &p[i], &v[i]);
    
            for(i=n-1; i>=0; i--) query(p[i]+1, 1, n, 1);
    
            for(int i=1; i<=n; i++) printf("%d ", ans[i]);
            cout<< endl;
        }
        return 0;
    }
    
  • 在直角座標系中,每一個星星佔據座標系上一個點,對於一個星星,它的 level 定義爲標繫上在它正下方、正左方以及左下方範圍內的星星的個數。由於輸入採取的原則是按照座標從左向右、從上到下的原則輸入的,忽略縱座標,只考慮橫座標,相當於在線段上的點上重複放置星星,對於星星 (x, y) 只需要查詢區間 [1, x] 內在它之前一共放置了多少個星星就可以了。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    #include<algorithm>
    #include<string.h>
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    using namespace std;
    const int MAX = 32e3+5;
    int sum[MAX<<2], ans[MAX];
    int n, x, y;
    
    void push_up(int rt){
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    
    //void build(int l, int r, int rt){
    //    if(l==r){
    //        sum[rt] = 0;
    //        return ;
    //    }
    //    int mid = (l+r)>>1;
    //    build(lson);
    //    build(rson);
    //    push_up(rt);
    //}
    
    int query(int b, int e, int l, int r, int rt){
        if(b<=l && e>=r){
            return sum[rt];
        }
        int mid = (l+r)>>1;
        int ans = 0;
        if(b<=mid) ans += query(b, e, lson);
        if(e>=mid+1) ans += query(b, e, rson);
        return ans;
    }
    
    void update(int x, int l, int r, int rt){
        if(l==r){
            sum[rt]++;
            return ;
        }
        int mid = (l+r)>>1;
        if(x<=mid) update(x, lson);
        else update(x, rson);
        push_up(rt);
    }
    
    int main(){
        scanf("%d", &n);
        for(int i=0; i<n; i++){
            scanf("%d%d", &x, &y);
            ans[query(1, x+1, 1, 32e3+1, 1)]++;
            update(x+1, 1, 32e3+1, 1);
        }
        for(int i=0; i<n; i++) printf("%d\n", ans[i]);
        return 0;
    }
    
  • 約瑟夫環 + 打表 + 線段樹。
    如果一個小孩兒第 n 個出隊,那麼他得到的糖果就是 n 的因子的個數。然後出隊的這個小孩兒手中有一張卡片,寫了下一個倒黴蛋的位置 a ,規則是:a > 0,從他開始順時針第 a 個人出隊; a < 0,從他開始逆時針第 a 個人出隊。
    要找到糖果數最多的小孩兒,就要找n個小孩中第幾個出隊的因子數最多,也就是 1 → n 中哪個數的因子最多。
    爲了避免超時,就要提前打表,找出每個數的因子個數。打表的時候也要考慮打表的方法,我採用的方法是,對於一個數,它的所有倍數因子數加一。
    然後就是用線段樹模擬約瑟夫環。我採用的方法是看要出隊的小孩兒前面(包含這個小孩兒)有幾個小孩兒,從而確定要出隊的小孩兒的位置。至於有幾個小孩兒,拿出筆畫畫很容易發現規律,這裏我就直接寫出來了:
    卡片上的數是正數:num = ((num-1+children[id].k-1)%sum[1] + sum[1])%sum[1]+1;
    負數:num = ((num-1+children[id].k)%sum[1]+sum[1])%sum[1]+1;
    這裏注意取模 +1 就好,取模是爲了模擬循環,+1 是線段樹的點是從 1 開始的。

  • 代碼

    #include<iostream>
    #include<stdio.h>
    #include<algorithm>
    #include<string.h>
    #define lson l, mid, rt<<1
    #define rson mid+1, r, rt<<1|1
    using namespace std;
    const int MAX = 5e5+5;
    int sum[MAX<<2], ans[MAX];
    int n, p, mod;
    struct{
        int k;
        char name[11];
    }children[MAX];
    
    void init(){
        for(int i=1; i<=MAX; i++){
            ans[i]++;
            for(int j=i+i; j<=MAX; j+=i){
                ans[j]++;
            }
        }
    }
    
    int the_max(int n){
        int m = ans[1], j = 1;
        for(int i=2; i<=n; i++){
            if(m<ans[i]){
                m = ans[i];
                j = i;
            }
        }
        return j;
    }
    
    void push_up(int rt){
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    }
    
    void build(int l, int r, int rt){
        if(l==r){
            sum[rt] = 1;
            return ;
        }
        int mid = (l+r)>>1;
        build(lson);
        build(rson);
        push_up(rt);
    }
    
    int query(int x, int l, int r, int rt){
        if(l==r) return r;
        int mid = (l+r)>>1;
        if(x<=sum[rt<<1]) query(x, lson);
        else query(x-sum[rt<<1], rson);
    }
    
    void update(int x, int l, int r, int rt){
        if(l==r && l==x){
            sum[rt] = 0;
            return ;
        }
        int mid = (l+r)>>1;
        if(x<=mid) update(x, lson);
        else update(x, rson);
        push_up(rt);
    }
    
    int main(){
        init();
        while(~scanf("%d%d", &n, &p)){
            for(int i=1; i<=n; i++) scanf("%s%d", children[i].name, &children[i].k);
            build(1, n, 1);
            int k = the_max(n), id = p, num = p;
            for(int i=1; i<k; i++){
                update(id, 1, n, 1);
                if(sum[1]==0) break;
                if(children[id].k<0) num = ((num-1+children[id].k)%sum[1]+sum[1])%sum[1]+1;
                else num = ((num-1+children[id].k-1)%sum[1] + sum[1])%sum[1]+1;
                id = query(num, 1, n, 1);
            }
            printf("%s %d\n", children[id].name, ans[k]);
        }
        return 0;
    }
    
發佈了9 篇原創文章 · 獲贊 1 · 訪問量 1350
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章