算法日记13:SC41树状数组(区间修改)

一、题目:

在这里插入图片描述

二、题解:

在单点修改中,我们用t[i]来维护原数组

2.1:在区间修改中,我们将维护原数组的差分数组

  • 接下来,让我们来回顾一些差分的性质
    在这里插入图片描述
  • 此时,假设我们需要求 a 1 + a 2 + a 3 + a 4 a1+a2+a3+a4 a1+a2+a3+a4的和,并且是直接通过 d [ i ] d[i] d[i]来求,我们可以发现:
    在这里插入图片描述
  • 推广到 n n n可以得到
    在这里插入图片描述

2.2:因此我们可以维护两个数组,一个为 t d [ i ] td[i] td[i](PS:因为n+1是常量),一个为 i ∗ t d [ i ] i*td[i] itd[i],这样就可以直接通过 d [ i ] d[i] d[i]来进行区间查询

  • t d [ i ] td[i] td[i]表示的是: d [ i ] d[i] d[i] [ i − l o w b i t ( i ) + 1 , i ] [i-lowbit(i)+1,i] [ilowbit(i)+1,i]的值(PS:相当于差分时,对差分数组做前缀和映射到原数组)
    在这里插入图片描述

2.3:相较于单点修改的差别

1、变量定义:

ll td[N];  //维护树状数组的差分
ll tdi[N]; //维护td[i]*i

2、更新树状数组–>更新树状差分数组单点修改(相当于在 i i i + a [ i ] +a[i] +a[i])

	//初始化树状差分数组
    for (int i = 1; i <= n; i++) 
    {
        update(i, a[i]);
        update(i+1,-a[i]);
    }

3、对于操作1(区间修改)变化:运用差分性质( ( a [ l ] − > a [ r ] ) + v = = ( d [ l ] + v ) , d [ r + 1 ] − v (a[l]->a[r])+v==(d[l]+v),d[r+1]-v (a[l]>a[r])+v==(d[l]+v),d[r+1]v)

		int op; cin >> op;
        if (op == 1)
        {
            ll l, r , v; cin >> l >> r >> v;
            update(l, v);   //维护差分数组
            update(r+1,-v);
        }

4、 u p d a t e ( ) update() update()更新函数的变化:

  • 1):与单点修改思路一致,差分数组( t d [ i ] td[i] td[i])对于其每一个数字的管辖区间 + v +v +v
  • 2):对于 t d i [ i ] tdi[i] tdi[i],也就是 i ∗ t d [ i ] i*td[i] itd[i],每一个元素都应该 + + +传入函数的原始值 ( x ∗ v ) (x*v) xv如图:而不应该 ( i ∗ v ) (i*v) (iv)因为此时 t d i [ x ] tdi[x] tdi[x]的增加影响了 t d i [ i ] tdi[i] tdi[i]的值如果写成了 ( i ∗ v ) (i*v) (iv)就变成了 ( 4 ∗ 2 ) (4*2) 42,但是实际应该为 ( 3 ∗ 2 ) (3*2) (32)
    在这里插入图片描述
void update(ll x, ll v)   //第 x 个值变化了 v
{
    for (int i = x; i <= n; i += lowbit(i))
    {
        td[i] += v; //维护td
        tdi[i]+=x*v; //维护td*i
    }
}

5、 g e t s u m ( ) getsum() getsum()求和函数的变化

  • 1、由推广公式可以得到求和函数
  • 2、类比前缀和的性质,求解 g e t s u m ( r ) − g e t s u m ( l − 1 ) getsum(r) - getsum(l - 1) getsum(r)getsum(l1)即可得出 [ l , r ] [l,r] [l,r]的和
    在这里插入图片描述
ll getsum(ll x)   //区间查询
{
    ll sum = 0;
    for (int i = x; i >= 1; i-=lowbit(i))
    {
        sum += (x+1)*td[i]-tdi[i];
    }
    return sum;
}
ll l, r; cin >> l >> r;
cout << getsum(r) - getsum(l - 1) << '\n';

三、完整代码如下:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 3e5 + 7;
ll a[N];
ll td[N];  //维护树状数组的差分
ll tdi[N]; //维护td[i]*i
ll n, q;

ll lowbit(ll x)   //树状数组底层函数
{
    return x & -x;
}

void update(ll x, ll v)   //第 x 个值变化了 v
{
    for (int i = x; i <= n; i += lowbit(i))
    {
        td[i] += v; //维护td
        tdi[i]+=x*v; //维护td*i
    }
}

ll getsum(ll x)   //区间查询
{
    ll sum = 0;
    for (int i = x; i >= 1; i-=lowbit(i))
    {
        sum += (x+1)*td[i]-tdi[i];	//求和公式
    }
    return sum;
}

void solve()
{
    cin >> n >> q ;
    for (int i = 1; i <= n; i++) cin >> a[i];

    //初始化树状数组
    for (int i = 1; i <= n; i++) 
    {
        update(i, a[i]);
        update(i+1,-a[i]);
    }
    while (q--) //q次询问
    {
        int op; cin >> op;
        if (op == 1)
        {
            ll l, r , v; cin >> l >> r >> v;
            update(l, v);   //维护差分数组
            update(r+1,-v);
        }
        else
        {
            ll l, r; cin >> l >> r;
            cout << getsum(r) - getsum(l - 1) << '\n';
        }
    }
}


int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int _ = 1; //cin >> _;
    while (_--) solve();
    system("pause");

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值