zoukankan      html  css  js  c++  java
  • 【模板篇】树状数组们(三)

    UPD: 本篇有了一个更通(hui)俗(se)易(nan)懂的讲解, 大家可以移步这里围观~(使用了latex推柿子, 带给你不一样的清新体验~

    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]的区间和了。。
    对就是这样。

  • 相关阅读:
    AJAX POST请求中参数以form data和request payload形式在servlet中的获取方式
    Java中List集合去除重复数据的方法
    java.util.Date、java.sql.Date、java.sql.Time、java.sql.Timestamp区别和总结
    Spring 中配置log4j日志功能
    log4j配置文件加载方式
    程序中使用log4J打印信息的两种方式
    elasticsearch常用命令
    接私活必备的10个开源项目??
    初识Elasticsearch
    常用在线工具
  • 原文地址:https://www.cnblogs.com/enzymii/p/8412157.html
Copyright © 2011-2022 走看看