树状数组学习(例题及题解)

在这里插入图片描述
树状数组:可以动态维护前缀和,查询时间复杂度O(log2n),修改时间复杂度O(log2n)。
树状数组记录前缀和的方式为这个数的二进制表示中从长度是从右往左的第一个0的大小,即长度为x-lowbit(x)+1。
代码模板

int tr[N];
int lowbit(int x)   //求第一个0的位置
{
    return x & -x;
}

void add(int x, int c) 在位置为x的地方加上c
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int sum(int x) 求x的前缀和
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

树状数组题解
1.楼兰图腾

大致思路:对于点i,求出左边和右边比i小的个数相乘,即为在i点∧的个数,同理,求出左边和右边比i大的个数相乘,即为在i点V的个数。
故可以用两个数组记录
Low[i]表示左边比第i个位置小的数的个数
Great[i]表示左边比第i个位置大的数的个数
tr[i]求和表示用于记录小于等于i个数的总和

const int N=200010;

int n;
int a[N],tr[N];
int great[N],low[N];

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

void add(int x,int c)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tr[i]+=c;
}

int sum(int x)
{
    int res=0;
    for(int i=x;i;i-=lowbit(i))
        res+=tr[i];
    
    return res;
}
int main()
{
    cin>>n;
    
    for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    
    for(int i=1;i<=n;i++)    从左往右遍历
    {
        int y=a[i];
        
        low[i]=sum(y-1);  
        great[i]=sum(n)-sum(y);
        
        add(y,1);
    }
    
    int res1=0,res2=0;
    memset(tr,0,sizeof tr); 
    
    for(int i=n;i;i--)  从右往左遍历出右边比i大和小的数
    {
        int y=a[i];
        
         res1+=(sum(n)-sum(y))*great[i];
         res2+=sum(y-1)*low[i];
        
        add(y,1);
    }
    
    printf("%d\n%d",res1,res2);
    
    return 0;
}

2.一个简单的整数问题
大致思路:本题是将树状数组和差分相结合,动态维护差分数组。
bi为差分数组,则Ai=b1+b2+b3+…+bi。
即l,r加上d就变为数组l位置加上d,r+1位置减去d。
差分数组bi=A[i]-A[i-1]
读入时

 for (int i = 1; i <= n; i ++ ) add(i, a[i] - a[i - 1]);

查询时

printf("%lld\n", sum(x))

修改时

add(l, d), add(r + 1, -d);

3.一个简单的整数问题2
在这里插入图片描述
题目相较于上一个变为查询l~r之和。
差分数组计算变为
在这里插入图片描述
将整个数组的矩阵列出
在这里插入图片描述
然后将空余的地方补齐
在这里插入图片描述
那么可发现
在这里插入图片描述
对于数组A的(1到x)的前缀和可表示为差分数组b(1到x)的前缀和×(x+1)-(i*bi)的前缀和
那么构建数组tr1维护bi的前缀和
构建数组tr2维护i*bi的前缀和

维护树状数组操作

ll a[N];
ll tr1[N],tr2[N];

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

void add(ll tr[],int x,ll k)
{
    for(int i=x;i<=n;i+=lowbit(i)) 
        tr[i]+=k;
}

ll sum(ll tr[],int x)
{
    ll res=0;
    for(int i=x;i;i-=lowbit(i))
        res+=tr[i];
    return res;
}

ll pre_sum(int x)  //计算A数组1~x的前缀和
{
    return sum(tr1,x)*(x+1)-sum(tr2,x);
}

读入时操作

for(int i=1;i<=n;i++)
    {
        int b=a[i]-a[i-1];
        add(tr1,i,b);
        add(tr2,i,(ll)i*b);
    }

查询时操作

cout<<pre_sum(r)-pre_sum(l-1)<<endl;

修改时操作

add(tr1,l,d);
            add(tr1,r+1,-d);
            add(tr2,l,l*d);
            add(tr2,r+1,(r+1)*-d);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值