树状数组

树状数组

在这里插入图片描述
每一列最顶端结点上色
在这里插入图片描述
加上二进制
在这里插入图片描述
我们假设
紫色的数组是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;
}

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