前缀和与数状数组

前缀和技巧适⽤于快速、频繁地计算⼀个索引区间内的元素之和

但当区间内的元素发生变化后,再求元素之和就会复杂得多,这时就用到了树状数组

首先让我们从一道题目入手

对于一个算法新手来说,可能只会想到暴力做法

每次求范围都进行一次for循环

whilie(m--)
{
    cin>>l>>r;
    int sum=0;
    for(int i=;i<=r;i++) sum+=a[i];
    cout<<ans<<endl;
}

思考一下暴力做法的时间复杂度,会发现是O(mn),在极端的情况下是10的12次方,会超时

这时候就是需要算法优化——前缀和算法

我们可以把把a[N]看成是一个数列,求出每一位的前n项和s[N]。

之后当我们再求某个区间内的和时,就不需要再进行一次for循环。

直接用前r个数的和,减去前l个数的和,

得到的就是l与r之间数的和。

S[i] = a[1] + a[2] + ... a[i];
a[l] + ... + a[r] = S[r] - S[l - 1];

//最最重要的是思想
for(int i=1;i<=n;i++)
	S[i] = S[i-1]+a[i];
    //每个i都有其对应的前缀和

什么是前缀和

原数组: a[1], a[2], a[3], a[4], a[5], …, a[n]

前缀和 S[i]为数组的前 i项和

前缀和: S[i] = a[1] + a[2] + a[3] + … + a[i]

注意: 前缀和的下标一定要从 1开始, 避免进行下标的转换

s[0] = 0

s[1] = s[0]+a[1]

前缀和的作用

快速求出元素组中某段区间的和 

这时候我们要思考一个问题,在这个题目中,是根据原始的a[N]数组算出的前缀和,在之后的每次查询中,原数组a[N]和前缀和s[N]都没有发生变化。倘若我们改变原数组a[N]中某位数的值,我们又该如何求某段区间的和哪

动态求连续区间和

我们可以用刚刚学的知识——前缀和

题目中a[N]每改变一次,我们就更新她的前缀和

for(int i=1;i<=n;i++) cin>>a[i];
    
    for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
    
    while(m--)
    {
        int k,a,b;
        cin>>k>>a>>b;
        if(k==0)
        {
            cout<<s[b]-s[a-1]<<endl;
        }
        else {
            for(int i=a;i<=n;i++) s[i]+=b;
        }
    }

再思考一下时间复杂度,和上一题的暴力枚举做法一样,O(mn),显然超时

那么,该怎么进行优化哪

根据经验,至少要把时间复杂度优化到nlogn;

在上面这段代码中,循环m次是不可能改变的,因为就是要进行m次询问嘛

要想下如何优化这部分代码,

for(int i=a;i<=n;i++) s[i]+=b;

也就是优化前缀和

因此

伟大的Peter M. Fenwick想出了树状数组,用于高效计算数列的前缀和, 区间和。

树状数组可以解决的问题

可以解决大部分基于区间上的更新(单点修改)以及求和问题(区间查询)

如果非要一句话说明树状数组 :将一个前缀和划分成多个子序列的和

按照Peter M. Fenwick的说法,正如所有的整数都可以表示成2的幂和,我们也可以把一串序列表示成一系列子序列的和。

采用这个想法,我们可将一个前缀和划分成多个子序列的和,而划分的方法与数的2的幂和具有极其相似的方式。

C[i] = A[i - 2k+1] + A[i - 2k+2] + ... + A[i];

//k为i的二进制中从最低位到高位连续零的长度

//返回x的最后一位1
int lowbit(int)
{
    return x & -x;
}

//add(x,y):在x位置加上y,并将后面相关联的位置也加上y
void add(int x,int y)
{
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=y;
}

//query(x):询问x的前缀和
int query(int x)
{
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}

主函数

int main()
{
    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    
    //求初始树状数组
    for(int i=1;i<=n;i++) add(i,a[i]);
    
    while(m--)
    {
        int k,x,y;
        scanf("%d%d%d",&k,&x,&y);
        if(k==0) 
        {
            int ans=query(y)-query(x-1);
            printf("%d\n",ans);
        }
        else add(x,y);
    }
    
    return 0;
}

这时的时间复杂度为O(mn)

结束~~~

若有收获,就点个赞吧~~~

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XSS9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值