樹狀數組

樹狀數組

在這裏插入圖片描述
每一列最頂端結點上色
在這裏插入圖片描述
加上二進制
在這裏插入圖片描述
我們假設
紫色的數組是top[1-8]
葉節點leave[1-8]
前i項葉節點和是sum[1-8]

以sum[6]舉例子
sum[6]=top[6]+top[4]
二進制翻譯
sum[110]=top[110]+top[100]

以sum[7]舉例子
sum[7]=top[7]+top[6]+top[4]
二進制翻譯
sum[111]=sum[111]+sum[110]+sum[100];

是不是大概發現了規律?
在這裏插入圖片描述
我們可以發現二進制末尾0的長度表示的是他包含的子節點的個數

我們假設求sum[7]也就是前7項和
那麼我們需要枚舉leave[1-7]
1.top[111]末尾2^0次方個數,也就是包含了1個數,
那麼只能是leave[7]

2.top[110]末尾2^1次方個數,所以包含了2個數,
那麼只能是leave[6],leave[5]

3.top[100]末尾2^2次方個數,包含了4個數,
那麼只能是leave[4],leave[3],leave[2],leave[1]

那麼sum[7]=top[111]+top[110]+top[100];
接着我們來介紹下這個lowbit函數

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

lowbit函數他的作用是什麼呢?就是尋找最低位的1的位置。
比如
lowbit(7)=001;
lowbit(6)=010;
lowbit(4)=100;
我們來研究下他的原理,&運算,當且僅當1&1=1,那麼一個數怎麼得到他的負數。就是一直取反然後+1,那麼這個1就會出現在一開始位置爲1的地方。然後取&運算,你就能發現只有這個+1的位置爲1,其他都爲0。
接着我們來看看關係
sum[7]=top[7]+top[7-lowbit(7)]+top[7-lowbit(7)-lowbit(7-lowbit(7))];
也就是求和要運用到lowbit的減法運算
接着我們想想端點更新
我們假設現在更新top[5],那麼我們還需要更新top[6],以及top[8]。
那麼我們看到
6=5+lowbit(5);
8=6+lowbit(6);
所以端點修改,就用lowbit的加法。
完整代碼如下:

#include<iostream>
using namespace std;
int top[100];
int n;//(n<100)
int lowbit(int x)
{
    return x&(-x);
}
void updata(int i,int num)//端點更新
{
    for(int j=i;j<=n;j=j+lowbit(j))
    {
        top[j]+=num;
    }
}
int getsum(int i)//求區間和
{
    int sum=0;
    for(int j=i;j>0;j=j-lowbit(j))
    {
        sum+=top[j];
    }
    return sum;
}
int main ()
{

    cin>>n;
    int num;
    for(int i=1;i<=n;i++)
    {
        cin>>num;
        updata(i,num);
    }
    cout<<"整個區間和爲:"<<getsum(n)<<endl;
    return 0;
}

數據

10
1 2 3 4 5 6 7 8 9 10
整個區間和爲:55

樹狀數組求逆序對的個數
逆序數就是對於數組a[i]中有下標i<j且a[i]>a[j]就算作是一對逆序數。
這個時候如果我們利用樹狀數組的特點,即下標的標記,就能實現求逆序數的對數。
我們舉一個例子對數字10 6 9 5 2求它的逆序數,首先我們先進行離散化處理爲
5 3 4 2 1。因爲我們是利用下標,所以離散化可以節省空間
然後我們寫在來處理這段數據。
i=1,我們現在updata(5,1)的話,此時被標記的只有5,那麼getsum(5)是指1到5的1的個數,那麼此時逆序數就是i-getsum(5)=0;
i=2,updata(3,1)的話,此時3也被標記了,然後我們getsum(3)=1,指的是1到3之間只有1個被標記。此時逆序數是多少呢?i表示的是第i個元素,getsum(i)表示的是比i小的數的個數,那麼此時比他大的個數就是i-getsum(i)=1;

後面類推…
這裏之所以不用普通的數組而用線段數組的原因就是線段樹組求和非常的方便!

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
struct node
{
    int pos;//位置
    int num;//數值
}data[100];
struct cmp
{
    bool operator()(struct node a,struct node b)
    {
        return a.num<b.num;
    }
};
int top[100];
int x[100];//儲存數據
int n;//(n<100)
int lowbit(int x)
{
    return x&(-x);
}
void updata(int i,int num)//端點更新
{
    for(int j=i;j<=n;j=j+lowbit(j))
    {
        top[j]+=num;
    }
}
int getsum(int i)//求區間和
{
    int sum=0;
    for(int j=i;j>0;j=j-lowbit(j))
    {
        sum+=top[j];
    }
    return sum;
}
int main ()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>data[i].num;
        data[i].pos=i;
    }
    sort(data+1,data+1+n,cmp());
    for(int i=1;i<=n;i++)
    {
        x[data[i].pos]=i;
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        updata(x[i],1);
        ans+=i-getsum(x[i]);
    }
    cout<<"有序對爲:"<<ans<<endl;
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章