题381.树状数组-acwing-Q241--楼兰图腾


题381.树状数组-acwing-Q241–楼兰图腾


一、题目

在这里插入图片描述
在这里插入图片描述

二、题解

本题要你求出输入的序列中构成的A和V的个数,我们会想着以A、V中心点分类,序列有多少个数就可以分几大类。以V为例,以中心点分类,每一种中心点将序列分成了左边和右边,则要想构成V,设中心点为i,则需满足y[i-j]>y[i],y[i+k]>y[i],则左边有多少个i-j对应的元素比y[i]小,左边选法就有多少个,右边同理,则设左边有a中选法,右边有b种,显然对于该中心点就有a*b种选法,对所有中心点的选法求和,便为最终V的个数。统计A细节相反,但同理。

那么现在的问题是要如何统计i左边的比y[i]来的小or大的数,i右边的比y[i]来的小or大的数。我们可以想着用一个树状数组t来维护1-n的每个整数出现的个数的前缀和,则以统计小为例子,只需要从左往右遍历序列,对于每一次遍历,有当前数y[i],则求t中1到y[i]-1的数的个数的前缀和即可,由于是从左往右遍历,所以一定可以保证求得是i左边的比y[i]来的小的数的个数,右边同理。统计大,细节相反,且同理。

综上,本题代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn=2e5+2;

int n;
int a[maxn],t[maxn];//树状数组t用于维护输入的序列中遍历到的1-n的每个整数的个数的前缀和
int lower[maxn],bigger[maxn];//lower[i]表示从左往右去遍历序列,比a[i]小的数的个数;bigger[i]表示从左往右去遍历序列,比a[i]大的数的个数

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

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

int ask(int x)
{
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
    {
        ans+=t[i];
    }
    return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)//从左往右遍历序列
    {
        int x=a[i];
        lower[i]=ask(x-1);//得到位于当前数x(即序列的第i个数a[i])左边的比x来的小的数的个数
        bigger[i]=ask(n)-ask(x);//得到位于当前数x(即序列的第i个数)左边的比x来的大的数的个数
        add(x,1);//统计x的个数,即在t中把x做单点修改,数+1
    }
    memset(t,0,sizeof t);//清空t
    ll res_low=0,res_big=0;
    for(int i=n;i>=1;i--)//从右往左遍历序列
    {
        int x=a[i];
        res_low+=(ll)lower[i]*ask(x-1);//得到位于当前数x(即序列的第i个数a[i])右边的比x来的小的数的个数并将其与此前求得的左边的答案lower[x]做乘积计入到res_low中,做A的方案数统计
        res_big+=(ll)bigger[i]*(ask(n)-ask(x));//同理
        add(x,1);
    }
    printf("%lld %lld",res_big,res_low);
}

三、关于树状数组

1.lowbit操作

lowbit(x)=x&-x

定义:表示非负整数x在二进制下最低位1及其后面的0构成的数值,例如4=100,则lowbit(4)=4

2.树状数组简介(图转自此b站视频)
在这里插入图片描述

用一个数组t作为树状数组来维护某原数组a的前缀和,t[x]等于区间[x-lowbit(x)+1,x]的a数组元素的和,区间长度为lowbit(x),建立后的示意图如上图所示。

(1)单点修改

对原数组a的某个下标对应的元素a[i]做+d操作,则对于树状数组那个下标对应的t[i]及其祖先节点的元素都要做+d操作,根据图像可知t[i]的祖先节点元素的数组下标就是每次i+lowbit(i),则有板子如下:

//单点修改
void add(int x,int d)
{
    for(int i=x;i<=n;i+=lowbit(i)) t[i]+=d;
}

(2)查询前缀和

对于原数组求到i的前缀和,则就a从1开始一直加到i,根据图像易知等价于对树状数组往左上方向的每一个t的元素求和(例如根据图像我们可求到下标为5的前缀和为t[4]+t[5]=(a[1]+a[2]+a[3]+a[4])+a[5]),下标变化为i-lowbit(i),则有板子如下:

//查询前缀和
int ask(int x)
{
    int ans=0;
    for(int i=x;i;i-=lowbit(i)) ans+=t[i];
    return ans;
}

3.操作总结
(1)单点修改、单点查询

//维护原数组a的前缀和
add(x,k);
ask(x)-ask(x-1)

(2)单点修改、区间查询

//维护原数组a的前缀和
add(x,k);
ask(r)-ask(l-1)

(3)区间修改、单点查询

//维护原数组a的差分数组b的前缀和
for(i) add(i,b) 

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

4.tip

(1)对于要维护什么数组的前缀和具体依题目而定,看你要求什么前缀和对求解有直接或者间接帮助
(2)初始化树状数组往里头加数借用的操作是add


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树状数组是一种用于加速前缀和操作的数据结构。它可以在O(logn)的时间复杂度内更新单个元素,并且可以在O(logn)的时间复杂度内查询一个区间的和。\[1\] 树状数组的基本思想是将数组分解成若干个长度为2的幂次的区间,每个区间的和都可以通过一系列区间和的累加得到。树状数组的每个节点都存储了一段区间的和,通过不断迭代lowbit()运算,可以得到从1到x之间的和。\[3\] 在树状数组的实现中,可以使用add()函数来更新单个元素的值,使用query()函数来查询一个区间的和。add()函数通过迭代lowbit()运算,将更新的值加到对应的节点上。query()函数通过计算两个前缀和的差值来得到一个区间的和。\[2\] 差分树状数组树状数组的一种变体,它可以用来求解区间最大值。差分树状数组的基本思想是将原始数组转化为差分数组,然后对差分数组建立树状数组。通过查询树状数组得到的前缀和,再加上差分数组的前缀和,就可以得到原始数组的区间最大值。\[2\] 综上所述,树状数组是一种用于加速前缀和操作的数据结构,可以在O(logn)的时间复杂度内更新单个元素和查询一个区间的和。差分树状数组树状数组的一种变体,用于求解区间最大值。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* *3* [RMQ问--------树状数组](https://blog.csdn.net/weixin_43743711/article/details/107191842)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值