• 树状数组 | 三道模板题小结


    今天写了一天北京信息科技大学校赛题,难度不大,跟西电校赛风格类似,大部分为数学题与规律题。(改日更)

    其中的I题为树状数组/线段树模板题,发现之前没有在博客总结,一时也找不到好的模板,于是重新深入学习了一番。

    首先引入洛谷上的模板题:

    P3374 【模板】树状数组 1

    题目描述

    如题,已知一个数列,你需要进行下面两种操作:

    1.将某一个数加上x

    2.求出某区间每一个数的和

    输入输出格式

    输入格式:

    第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。

    第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

    接下来M行每行包含3个整数,表示一个操作,具体如下:

    操作1: 格式:1 x k 含义:将第x个数加上k

    操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和

    输出格式:

    输出包含若干行整数,即为所有操作2的结果。

     

    说明

    时空限制:1000ms,128M

    数据规模:

    对于30%的数据:N<=8,M<=10

    对于70%的数据:N<=10000,M<=10000

    对于100%的数据:N<=500000,M<=500000

    题解

        由于是【单点修改,区间查询】裸题,就直接上AC代码了:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define lowbit(x) ((x)&(-x))
    
    int n, m;
    int C[500010];    // 树状数组只需要开一倍内存空间
    int sum(int x) {
        int res = 0;
        while(x) {
            res += C[x];
            x -= lowbit(x);
        }
        return res;
    }
    void add(int x, int d) {
        while(x<=n) {
            C[x] += d;
            x += lowbit(x);
        }
    }
    
    int main() {
        cin>>n>>m;
        for(int i=1;i<=n;i++) {
            int ai;
            scanf("%d", &ai);
            add(i, ai);
        }
        while(m--) {
            int q, x, y;
            scanf("%d %d %d", &q, &x, &y);
            if(q==1) {
                add(x, y);
            }
            else if(q==2) {
                printf("%d
    ", sum(y)-sum(x-1));
            }
        }
        return 0;
    }

    P3368 【模板】树状数组 2

    题目描述

    如题,已知一个数列,你需要进行下面两种操作:

    1.将某区间每一个数数加上x

    2.求出某一个数的值

    输入输出格式

    输入格式

    第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。

    第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

    接下来M行每行包含2或4个整数,表示一个操作,具体如下:

    操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

    操作2: 格式:2 x 含义:输出第x个数的值

    输出格式

    输出包含若干行整数,即为所有操作2的结果。

     

    说明

    时空限制:1000ms,128M

    数据规模:

    对于30%的数据:N<=8,M<=10

    对于70%的数据:N<=10000,M<=10000

    对于100%的数据:N<=500000,M<=500000

    题解

        【区间修改,单点查询】跟标准的状数组模板不太一样,不能直接套模板。(改用的线段树还没理解,改日更)

        我们可以对问题进行转化。

        区间和利用了前缀和的思想,那么求单点/部分区间和只需要前缀和作差。

        如果先把原数组进行差分处理,那么对应的区间和就恢复为原数组单点的值,这样就能实现单点查询。

    来介绍一下差分

    设数组a[]={1,6,8,5,10},那么差分数组b[]={1,5,2,-3,5}

    也就是说b[i]=a[i]-a[i-1];(a[0]=0;),那么a[i]=b[1]+....+b[i];(这个很好证的)。

    假如区间[2,4]都加上2的话

    a数组变为a[]={1,8,10,7,10},b数组变为b={1,7,2,-3,3};

    发现了没有,b数组只有b[2]和b[5]变了,因为区间[2,4]是同时加上2的,所以在区间内b[i]-b[i-1]是不变的.

    所以对区间[x,y]进行修改,只用修改b[x]与b[y+1]:

    b[x]=b[x]+k;b[y+1]=b[y+1]-k;

        在看了题解差分的介绍后,我恍然大悟,随即独立写出以下AC代码:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define lowbit(x) ((x)&(-x))
    
    int n, m;
    int s[500010];
    int C[500010];  // 维护差分信息
    int sum(int x) {
        int res = 0;
        while(x) {
            res += C[x];
            x -= lowbit(x);
        }
        return res;
    }
    void add(int x, int d) {
        while(x<=n) {
            C[x] += d;
            x += lowbit(x);
        }
    }
    int main() {
        cin>>n>>m;
        for(int i=1;i<=n;i++) {
            scanf("%d", &s[i]);
        }
        while(m--) {
            int q, x, y, k;
            scanf("%d", &q);
            if(q==1) {
                scanf("%d %d %d", &x, &y, &k);
                add(x, k);
                add(y+1, -k);
            }
            else if(q==2) {
                scanf("%d", &x);
                printf("%d
    ", s[x]+sum(x));   // a[i]=b[1]+....+b[i]
                              // 而sum(x)结果为区间和,对应b[1]+...+b[x] } }
    return 0; }

    洛谷给出的题解代码:

    #include <iostream>
    #include <cstdio>
    #define lowbit(x) x & -x
    using namespace std;
    long long tree[500005];
    int n, m;
    void add(int x, long long num) {
        while (x <= n) {
            tree[x] += num;
            x += lowbit(x);
        }
    }
    long long query(int x) {
        long long ans = 0;
        while (x) {
            ans += tree[x];
            x -= lowbit(x);
        }
        return ans;
    }
    int main() {
        freopen("in.txt", "r", stdin);
        scanf("%d%d", &n, &m);
        long long last = 0, now;
        for (int i = 1; i <= n; i++) {
            scanf("%lld", &now);
            add(i, now - last);
            last = now;
        }
        int flg;
        while (m--) {
            scanf("%d", &flg);
            if (flg == 1) {
                int x, y;
                long long k;
                scanf("%d%d%lld", &x, &y, &k);
                add(x, k);
                add(y + 1, -k);
            } else if (flg == 2) {
                int x;
                scanf("%d", &x);
                printf("%lld
    ", query(x));
            }
        }
        return 0;
    }
    View Code

    I-andy种树

    链接:https://ac.nowcoder.com/acm/contest/940/I

    来源:牛客网

    题目描述

    andy在他的庄园里种了n棵树,排列成一排,标号为1到n。最开始的时候n棵树的高度都是0,也就是种子刚刚被埋下,树还没有长出来。

    andy会一种魔法,他每使用一次魔法,就可以让树标号落在连续区间[l, r]里的树的高度增加1。他可以使用q次这种魔法,然后他很好奇,在使用了q次魔法之后,他的所有树的高度分别是多少呢?

    输入描述

    第一行输入两个整数n,q。(1<= n, q <= 1e5)

    接下来q行,每行输入两个整数l, r(l <= r),表示andy让标号落在区间[l, r]里的数高度都加1

    输出描述

    输出有一行n个整数,每个整数后面有空格。

    输出末尾没有换行,第i个数表示第i棵树的高度

    
    

    示例

    输入

    10 3

    1 3

    2 4

    3 3

    输出

    1 2 3 1 0 0 0 0 0 0

    说明

    andy种了10棵树

    第一次使用魔法使得1、2、3棵树的高度增加1,所有树的高度为

    1 1 1 0 0 0 0 0 0 0

    第二次使用魔法使得2、3、4棵树的高度增加1,所有树的高度为

    1 2 2 1 0 0 0 0 0 0

    第三次使用魔法使得第3棵树的高度增加1,所有树的高度为

    1 2 3 1 0 0 0 0 0 0

    题解

       这是典型的区间更新,单点查询的问题,使用线段树的解法(参考):

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #define lson l, mid, rt << 1
    #define rson mid + 1, r, rt << 1 | 1
    using namespace std;
      
    int const MAX = 1e5 + 100;
    long long  mx[MAX<<2];
     
    void Update(int L, int R, int l, int r, int rt)
    {
        if(L<=l && R>=r)
        {
            mx[rt] += 1;
            return;
        }
        int mid = (l + r) >> 1;
        if(L<=mid)
            Update(L, R, lson);
        if(mid<R)
            Update(L, R, rson);
    }
       
    void Query(int l, int r, int rt, long long k)
    {
        if(l==r)
        {
            if(l==1)printf("%lld", mx[rt]+k);
            else printf(" %lld", mx[rt]+k);
            return;
        }
        int mid = (l + r) >> 1;
        Query(lson, mx[rt]+k);
        Query(rson, mx[rt]+k);
    }
       
    int main()
    {
        int n, q;
        cin>>n>>q;
      
        while(q--)
        {
            int l, r;
            scanf("%d %d", &l, &r);
            Update(l, r,  1, n, 1);
        }
        Query(1, n, 1, 0);
        printf("
    ");
      
        return 0;
    }
    View Code

        

        利用差分思想,我们考虑用数组b[i]记录树a[i]-a[i-1]的高度差,对区间[l, r]里的数进行+1操作只会影响数组b[l]与b[r+1]处的值,即

    b[l] = b[l] + 1

    b[r+1] = b[r+1] - 1

        而树状数组能在O(logn)内实现单点修改和区间求和的操作,总复杂度O(nlogn)。

        维护数组b[],sum(i)=a[i]=b[0]+...+b[i]即为第i颗树的高度。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define lowbit(x) ((x)&(-x))
    
    int n, m;
    int C[500010];  // 维护差分信息
    int sum(int x) {
        int res = 0;
        while(x) {
            res += C[x];
            x -= lowbit(x);
        }
        return res;
    }
    void add(int x, int d) {
        while(x<=n) {
            C[x] += d;
            x += lowbit(x);
        }
    }
    
    int main() {
        cin>>n>>m;
        while(m--) {
            int x, y;
            scanf("%d %d", &x, &y);
            add(x, 1);
            add(y+1, -1);                
        }
      
        printf("%d", sum(1));
        for(int i=2;i<=n;i++) {
             printf(" %d", sum(i));      
        }
    
        return 0;
    }

         (完)

  • 相关阅读:
    xampp 安装后无法启动apache 的解决方法
    前端常用规范
    FontAwesome 奥森图标的学习
    获取iframe 内元素的方法
    CSS中的选择器
    使用JavaScript缓存图片
    控制台console对象常用的一些方法
    清除浮动的方法
    浏览器存储:cookie
    HTML的文档类型:<!DOCTYPE >
  • 原文地址:https://www.cnblogs.com/izcat/p/11123609.html
Copyright © 2020-2023  润新知