1. 程式人生 > 實用技巧 >演算法學習之旅——樹狀陣列

演算法學習之旅——樹狀陣列

入坑一年了,現在才開始學樹狀陣列 ORZ ORZ ORZ


樹狀陣列最基本的應用場景:

(HDU P1166)敵兵佈陣

Problem Description
C國的死對頭A國這段時間正在進行軍事演習,所以C國間諜頭子Derek和他手下Tidy又開始忙乎了。A國在海岸線沿直線佈置了N個工兵營地,Derek和Tidy的任務就是要監視這些工兵營地的活動情況。由於採取了某種先進的監測手段,所以每個工兵營地的人數C國都掌握的一清二楚,每個工兵營地的人數都有可能發生變動,可能增加或減少若干人手,但這些都逃不過C國的監視。
中央情報局要研究敵人究竟演習什麼戰術,所以Tidy要隨時向Derek彙報某一段連續的工兵營地一共有多少人,例如Derek問:“Tidy,馬上彙報第3個營地到第10個營地共有多少人!”Tidy就要馬上開始計算這一段的總人數並彙報。但敵兵營地的人數經常變動,而Derek每次詢問的段都不一樣,所以Tidy不得不每次都一個一個營地的去數,很快就精疲力盡了,Derek對Tidy的計算速度越來越不滿:"你個死肥仔,算得這麼慢,我炒你魷魚!”Tidy想:“你自己來算算看,這可真是一項累人的工作!我恨不得你炒我魷魚呢!”無奈之下,Tidy只好打電話向計算機專家Windbreaker求救,Windbreaker說:“死肥仔,叫你平時做多點acm題和看多點演算法書,現在嚐到苦果了吧!”Tidy說:"我知錯了。。。"但Windbreaker已經掛掉電話了。Tidy很苦惱,這麼算他真的會崩潰的,聰明的讀者,你能寫個程式幫他完成這項工作嗎?不過如果你的程式效率不夠高的話,Tidy還是會受到Derek的責罵的.
Input
第一行一個整數T,表示有T組資料。
每組資料第一行一個正整數N(N<=50000),表示敵人有N個工兵營地,接下來有N個正整數,第i個正整數ai代表第i個工兵營地裡開始時有ai個人(1<=ai<=50)。
接下來每行有一條命令,命令有4種形式:
(1) Add i j,i和j為正整數,表示第i個營地增加j個人(j不超過30)
(2)Sub i j ,i和j為正整數,表示第i個營地減少j個人(j不超過30);
(3)Query i j ,i和j為正整數,i<=j,表示詢問第i到第j個營地的總人數;
(4)End 表示結束,這條命令在每組資料最後出現;
每組資料最多有40000條命令
Output
對第i組資料,首先輸出“Case i:”和回車,
對於每個Query詢問,輸出一個整數並回車,表示詢問的段中的總人數,這個數保持在int以內。

如果直接模擬,每次修改O(1),查詢O(n),1e5的資料肯定會T。

如果用差分陣列,每次修改O(n),查詢O(1),也會T。

所以就需要一種效能更優的演算法——樹狀陣列,當然線段樹也是可以的


AC程式碼

#include<bits/stdc++.h>

using namespace std;
const int maxn=50005;
int tree[maxn];//tree[i]代表(i-lowbit(i),i]區間內的和

inline int lowbit(int x){
    return x&(-x);
}
inline void update(int i,int
x){//單點修改 for(int pos=i;pos<maxn;pos+=lowbit(pos)) tree[pos]+=x; } inline query(int n){//求前n項和 int ans=0; for(int pos=n;pos>0;pos-=lowbit(pos)) ans+=tree[pos]; return ans; } inline query(int a,int b){ return query(b)-query(a-1); } int main(){ int t,n,a,b; cin
>>t; for(int j=1;j<=t;j++){ cout<<"Case "<<j<<":"<<endl; memset(tree,0,sizeof(tree)); cin>>n; for(int i=1;i<=n;i++){ int x; cin>>x; update(i,x); } string op; while(cin>>op){ if(op=="End") break; if(op=="Query"){ cin>>a>>b; cout<<query(a,b)<<endl; } if(op=="Add"){ cin>>a>>b; update(a,b); } if(op=="Sub"){ cin>>a>>b; update(a,-b); } } } }

(洛谷P1908) 逆序對

題目描述
貓貓TOM和小老鼠JERRY最近又較量上了,但是畢竟都是成年人,他們已經不喜歡再玩那種你追我趕的遊戲,現在他們喜歡玩統計。最近,TOM老貓查閱到一個人類稱之為“逆序對”的東西,這東西是這樣定義的:對於給定的一段正整數序列,逆序對就是序列中ai>aj且i<j的有序對。知道這概念後,他們就比賽誰先算出給定的一段正整數序列中逆序對的數目。
輸入格式
第一行,一個數n,表示序列中有n個數。
第二行n個數,表示給定的序列。序列中每個數字不超過10^9
輸出格式
給定序列中逆序對的數目。

解法

先將陣列離散化(也是第一次用ORZ)

然後求從後往前遍歷,記錄已經出現的小於ai的個數


AC程式碼

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=500005;
int tree[maxn];
inline int lowbit(int x){
    return x&(-x);
}
inline void update(int i,int x){
    for(int pos=i;pos<maxn;pos+=lowbit(pos))
        tree[pos]+=x;
}
inline int query(int n){
    int ans=0;
    for(int pos=n;pos>0;pos-=lowbit(pos))
        ans+=tree[pos];
    return ans;
}
inline int query(int a,int b){
    return query(b)-query(a-1);
}
struct node{
    int value,id;
    bool operator < (const node a){
        if(value==a.value)
            return id<a.id;
        else
            return value<a.value;
    }
};
int a[maxn];//離散化後的陣列
node b[maxn];
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>b[i].value;
        b[i].id=i;
    }
    sort(b+1,b+n+1);
    for(int i=1;i<=n;i++)
        a[b[i].id]=i;
    ll ans=0;
    for(int i=n;i>0;i--){
        ans+=query(a[i]);
        update(a[i],1);
    }
    cout<<ans<<endl;
}