【模板篇】树状数组们(三)

ok,以上两期稍稍讲了一下树状数组的基本功能。。

当然,把树状数组拉出来不能只有这两个功能对不对。。。(不然网上都有怎么把你们忽悠来看嘛)
树状数组还是有两把刷子的

(非战斗人员退散)
今天,我们要讲的是:

区间加,区间查

什么???
许多学过线段树的人该诧异了吧。。
树状数组还能干这事?
答案是可以的。。
(○| ̄|_在此%拜一下发明这种做法的神犇orz)

还记得上次我们的c变化成了差分数组吗?
树状数组就是擅长求前缀和和维护差分信息~
当然,这次的目标中有两个区间字样,所以要开两个数组(什么逻辑嘛……)

其实原因并不是这样。

我们用一个差分数组来保存相邻两个数据的差,这个数组命名为c1
此时,无论我们在树状数组上怎么乱搞,原数据的第i个点最后的值(记为a[i]吧)就是求一下c1[i]的总和
(嗯我们上一期的单点查)
对于区间修改,我们采取让c1[l]加和让c1[r+1]减的方式(还是差分)
对于区间查询,我们有ans=sum[r]-sum[l-1](How old are you,差分?)
于是很明显,最后原数组中第i个点乱搞一波后的值是sigma(c1[j]) (j=1..i)

你们理清楚没有。。

理清楚之后,
求1~i的和的时候,仔细看下面:

sum(i)=a[1]+a[2]+...+a[i]
      =c1[1]+(c1[1]+c1[2])+...+(c1[1]+c1[2]+...+c1[i])
      =i*c1[1]+(i-1)*c1[2]+...+1*c1[i]
      =i*(c1[1]+c1[2]+...+c1[i])-(0*c1[1]+1*c1[2]+...+(i-1)*c1[i])
      =i*sigma(c1[j])-sigma(c1[j]*(j-1)) (j=1..i)

所以,我们只要同时 维护一下sigma(c1[j])和sigma(c1[j]*(j-1))就行了。。
还记得c1的差分性质么
所以我们再用一个数组c2搞出sigma(c1[j]*(j-1)),在维护c1的时候顺手维护一下即可。。
这样复杂度也不会被改变!!!非常好而且奇妙的性质。。。
sum[i]就照着上面的式子搞就行。。

下面,终于到了代码,我有一件事情要说:其实只看代码就好,上面讲的没啥用233
代码的例子是用的luogu3372的【模板】线段树 1 (哈哈哈哈,用线段树的人们!)
题目传送门:
https://www.luogu.org/problem/show?pid=3372
里面的数据是要用long long的。。
你们自己看情况改就好了233
而且听说codevs1082的线段树练习3也可以用这种方法水过。。
这题的传送门:http://www.codevs.cn/problem/1082/
而且码长空间时间都要优于线段树哦。。

不过main函数我就不写了_ (:з」∠) _
而且这两个题都要开long long而代码里是没有开的

#include <cstdio>

#define gc getchar

int getnum() {
    int a = 0; char c = gc(); bool f = 0;
    for (; (c<'0' || c>'9') && c != '-'; c = gc());
    if (c == '-') f = 1, c = gc();
    for (; c >= '0'&&c <= '9'; c = gc()) a = (a << 1) + (a << 3) + c - '0';
    return f ? -a : a;
}

class Binary_Tree3 {

private:
    static const int MAXN = 200002;
    int c1[MAXN], c2[MAXN], n, a[MAXN];

    inline int lb(int x) {
        return x&-x;
    }

    int getnum() {
        int a = 0; char c = gc(); bool f = 0;
        for (; (c<'0' || c>'9') && c != '-'; c = gc());
        if (c == '-') f = 1, c = gc();
        for (; c >= '0'&&c <= '9'; c = gc()) a = (a << 1) + (a << 3) + c - '0';
        return f ? -a : a;
    }
public:

    void build(int sum) {
        n = sum;
        for (int i = 1; i <= sum; i++) {
            a[i] = getnum();
            add(c1, i, a[i] - a[i - 1]);
            add(c2, i, (i - 1) * (a[i] - a[i - 1]));
        }
    }

    void add(int *r, int x, int i) {
        for (; x <= n; x += lb(x))
            r[x] += i;
    }

    int ask(int *r, int x) {
        int s = 0;
        for (; x; x -= lb(x))
            s += r[x];
        return s;
    }

    void adda(int l, int r, int i) {
        add(c1, l, i); add(c1, r + 1, -i);
        add(c2, l, i * (l - 1)); add(c2, r + 1, -i * r);
    }

    int query(int l, int r) {
        return r * ask(c1, r) - ask(c2, r) - (l - 1) * ask(c1, l - 1) + ask(c2, l - 1);
    }

};

大概就是这个样子了。。
- 区间加的话就调用adda(l,r,i)就是区间[l,r]加i
- 区间查的话就输出query(l,r)就是区间[l,r]的区间和了。。
对就是这样。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值