StkOvflow

STACK OVERFLOW!

一言(ヒトコト)

你无聊吗,快去刷题。
——lmh

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

题目描述

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

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

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

输入格式

第一行包含两个整数 nm

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

接下来 m 行,每行包含两个整数 lr,表示一个询问的区间范围。

输出格式

m 行,每行输出一个询问的结果。

数据范围

1lrn,
1n,m100000,
10001000

输入样例:

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

输出样例:

    3
    6
    10

算法

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

前缀和

思路与推导

所谓前缀和,就是我们开一个s[]数组来维护a[]数组的前缀和
其中sx=i=1xai
如果我们要查询i=lrai的话
因为i=lrai+i=1l1ai=i=1rai
所以i=lrai=i=1raii=1l1ai
我们已经定义了sx=i=1xai
sx带入上面的算式可以得到
i=lrai=srsl1
我们这道题目只需用O(n)时间预处理
怎么预处理呢
sx=i=1xai,sx+1=i=1x+1ai=i=1xai+ax+1
所以我们只要确定了s1=a1
后面的部分循环即可

时间复杂度

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有一点大炮打蚊子的感觉,但也不失为一次练习

posted @   StkOvflow  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
点击右上角即可分享
微信分享提示