首页 > 其他分享 >795前缀和,线段树,树状数组

795前缀和,线段树,树状数组

时间:2022-12-20 16:33:06浏览次数:42  
标签:795 前缀 树状 int sum 数组 线段

题目描述

输入一个长度为 \(n\) 的整数序列。

接下来再输入 \(m\) 个询问,每个询问输入一对 \(l, r\)。

对于每个询问,输出原序列中从第 \(l\) 个数到第 \(r\) 个数的和。

输入格式

第一行包含两个整数 \(n\) 和 \(m\)。

第二行包含 \(n\) 个整数,表示整数数列。

接下来 \(m\) 行,每行包含两个整数 \(l\) 和 \(r\),表示一个询问的区间范围。

输出格式

共 \(m\) 行,每行输出一个询问的结果。

数据范围

\(1 \le l \le r \le n,\)
\(1 \le n,m \le 100000,\)
\(-1000 \le 数列中元素的值 \le 1000\)

输入样例:

    5 3
    2 1 3 6 4
    1 2
    1 3
    2 4

输出样例:

    3
    6
    10

算法

区间和这里提供三种做法:前缀和,线段树,树状数组

前缀和

思路与推导

所谓前缀和,就是我们开一个\(s[]\)数组来维护\(a[]\)数组的前缀和
其中\(s_{x} = \sum_{i = 1}^{x} a_{i}\)
如果我们要查询\(\sum_{i = l}^{r} a_{i}\)的话
因为\(\sum_{i=l}^{r} a_{i} + \sum_{i=1}^{l-1} a_{i} = \sum_{i = 1}^{r} a_{i}\)
所以\(\sum_{i=l}^{r} a_{i} = \sum_{i = 1}^{r} a_{i} - \sum_{i=1}^{l-1} a_{i}\)
我们已经定义了\(s_{x} = \sum_{i = 1}^{x} a_{i}\)
将\(s_x\)带入上面的算式可以得到
\(\sum_{i=l}^{r} a_{i} = s_{r} - s_{l-1}\)
我们这道题目只需用\(O(n)\)时间预处理
怎么预处理呢
\(s_{x} = \sum_{i = 1}^{x} a_{i}, s_{x + 1} = \sum_{i = 1}^{x + 1} a_{i} = \sum_{i=1}^{x} a_{i} + a_{x+1}\)
所以我们只要确定了\(s_{1} = a_{1}\)
后面的部分循环即可

时间复杂度

\(预处理O(n), 查询O(1)\)

代码
#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int n, a[N], m, s[N];

int main()
{
    scanf("%d%d", &n, &m);

    for(int i = 1;i <= n;i ++ )
        scanf("%d", &a[i]);
    
    //预处理s数组
    s[1] = a[1];
    for (int i = 2; i <= n; i ++ ) 
        s[i] = s[i - 1] + a[i];
    
    int l, r;
    while (m -- )
    {
        scanf("%d%d", &l, &r);
        printf("%d\n",s[r] - s[l - 1]);
    }

    return 0;
}

线段树

思路

这里就不具体说线段树了,这里要维护的区间问题是区间和,不需要修改,故线段树也很合适,开的结构体里面放\({l, r, sum}\)即可

时间复杂度

\(建树O(nlogn),查询O(logn)\)

代码
#include <iostream>

using namespace std;

const int N = 1e6 + 10;
int n, m, a[N];

struct Seg 
{ 
    int l, r, sum; 
} tr[N << 2]; //线段树要开四倍空间

void build(int p, int l, int r) //代表编号为p的节点管理[l, r]这个区间
{
    Seg& cur = tr[p];
    cur.l = l, cur.r = r;
    if (l == r) return void(cur.sum = a[l]); //叶子节点直接赋值
    int mid = cur.l + cur.r >> 1; //取中间点
    build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r); //左右建子树
    cur.sum = tr[p << 1].sum + tr[p << 1 | 1].sum; // pushup操作
}

int query(int p, int l, int r) 
{
    Seg& cur = tr[p];
    if (cur.l >= l && cur.r <= r) return cur.sum; // 完全包含直接返回
    int mid = cur.l + cur.r >> 1, s = 0;
    if (l <= mid) s += query(p << 1, l, r); //左子包含递归左子
    if (r > mid) s += query(p << 1 | 1, l, r); // 右字包含递归右子
    return s;
}

int main()
{
    scanf("%d%d", &n, &m);
    
    //读入并建树
    for (int i = 1; i <= n; i ++ ) 
        scanf("%d", &a[i]);
    build(1, 1, n);
    
    while (m -- ) 
    {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", query(1, l, r));
    }
    
    return 0;
}

树状数组

思路

树状数组本来就可以维护前缀
所以这里用树状数组
这里提供两种写法,一种是普通BIT,一种是面向对象泛型BIT

时间复杂度

\(建树O(nlogn),查询O(logn)\)
比线段树快

代码
普通写法
#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int tr[N], n, Q, a[N];

void add(int x, int v) 
{
    for (; x <= n; x += x & -x) tr[x] += v;
}

int query(int x) 
{
    int res = 0;
    for (; x; x -= x & -x) res += tr[x];
    return res;
}

int main()
{
    scanf("%d%d", &n, &Q);
    
    for (int i = 1; i <= n; i ++ ) 
        scanf("%d", &a[i]), add(i, a[i]);
    
    while (Q -- ) 
    {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", query(r) - query(l - 1));
    }
    
    return 0;
}
面向对象
#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int n, Q, a[N];

template<typename T> 

struct Bit
{
    T c[N];
    void add(T x, const T v) 
    {
        for (; x <= n; x += x & -x) c[x] += v;
    }

    T query(T x) 
    {
        T res = 0;
        for (; x; x -= x & -x) res += c[x];
        return res;
    }
} ; Bit<int> tr;

int main()
{
    scanf("%d%d", &n, &Q);
    
    for (int i = 1; i <= n; i ++ ) 
        scanf("%d", &a[i]), tr.add(i, a[i]);
    
    while (Q -- ) 
    {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", tr.query(r) - tr.query(l - 1));
    }
    
    return 0;
}

时间对比

\(前缀和100ms,线段树401ms,树状数组(1) 202ms, 树状数组(2) 223ms\)
撇去评测机的微小影响
速度排名是:前缀和,树状数组(1),树状数组(2),线段树

End

写线段树和BIT有一点大炮打蚊子的感觉,但也不失为一次练习

标签:795,前缀,树状,int,sum,数组,线段
From: https://www.cnblogs.com/StkOvflow/p/16994545.html

相关文章

  • vuejs处理树状结构的数据扁平化
    1,有这样一个数据:1data=[2{3"id":1,4"name":"吃喝",5"parentId":0,6"children":[7......
  • 前缀树(Tire)—Python
    核心思想空间换时间,是一种用于快速减速的多叉树结构,利用字符串的公共前缀来降低时间优缺点:优点:查询效率高,减少字符比较缺点:内存消耗较大每次都会从头向下一直到字符串......
  • 前缀和【模板题】
    前缀和输入一个长度为n的整数序列。接下来再输入m个询问,每个询问输入一对l,r。对于每个询问,输出原序列中从第l个数到第r个数的和。输入格式第一行包含两个整......
  • QTreewidget树状列表右击事件
     树状列表右击事件(添加删除修改等操作) 思路:首先我们需要一个voidcontextMenuEvent(QContextMenuEvent*event);管理Menu事件的一个接口此接口为系统自带的,不需......
  • 新增自定义前缀
    新增自定义前缀1.设置->自定义项2.选择发布者3.新建4.填写信息项,保存即可......
  • 二维偏序问题与树状数组在其中的运用
    链接:https://ac.nowcoder.com/acm/problem/247068来源:牛客网对于两个序列a,b,求一个l和r使得在min(区间和a,区间和b)最大。发现就是min(sum1[r]-sum1[l-1],sum2[r]-sum2[l-1]......
  • 拼多多 2020校招 多多的电子字典(字典树前缀搜索,DP)
    多多鸡打算造一本自己的电子字典,里面的所有单词都只由a和b组成。每个单词的组成里a的数量不能超过N个且b的数量不能超过M个。多多鸡的幸运数字是K,它打算把所有满足条件的......
  • 前缀树(字典树)
    一种树形结构,能够实现对字符串集的高效检索项目中有所应用,那么:如果说得明白,写得出来,那就是亮点反之,就是搬起石头砸自己的脚有必要好好研究下实现首先便是实现一颗......
  • 算法学习笔记(5)——前缀和与差分
    前缀和与差分前缀和与差分一维前缀和差分一维前缀和前缀和可以用于快速计算一个序列的区间和,也有很多问题里不是直接用前缀和,但是借用了前缀和的思想。用\(s[i......
  • 【数据结构】二维树状数组
    一、二维树状数组二维树状数组,其实就是一维的树状数组上的节点再套个树状数组,就变成了二维树状数组了。constintN=1e3+10;inttr[N][N],n,m;#definelowbit(x......