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

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

其中的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\n", 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\n", 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\n", 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("\n");
  
    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;
}

 


     (完)

 

posted @ 2019-07-02 22:40  izcat  阅读(566)  评论(0编辑  收藏  举报