离散化模板题(区间和)题解

本题是一个非常好的模板题,结合了前缀和、离散化的知识点。

题目

题目链接

AcWing的OJ,https://www.acwing.com/problem/content/804/

题目描述

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
近下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l, r] 之间的所有数的和。

输入

第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下里 m 行,每行包含两个整数 l 和 r。

输出

共 m 行,每行输出一个询问中所求的区间内数字和。

样例输入

3 3
1 2
3 6
7 5
1 3
4 6
7 8

样例输出

8
0
5

数据范围

−10^9 ≤ x ≤ 10^9,
1 ≤ n, m ≤ 10^5,
−10^9 ≤ l ≤ r ≤ 10^9,
−10000 ≤ c ≤ 10000

分析

数据范围分析

第一眼看到题目的时候,觉得题目好简单,就是一个标准的前缀和模板题。但是看到数据范围后,才发现好多坑。

第一,有一个无限长的数轴,数轴上每个坐标上的数都是 0。我们看到 x 的范围是 [−10^9, 10^9] ,也就是 2*10^9 大小,如果直接定义这么大的数组,估计要爆。

第二,实际操作的数据是 n,n 的范围是 [1, 10^5],也就是进过了 n 次操作后,需要插入的数据包括 n 个坐标和 m 个 [l, r],因此最多有 3*10^5 个数据是非零的,其他数据都是 0。3*10^5 对比 2*10^9,我们可以发现,非常符合离散化的前提。

第三,由于事前不知道 n 的大小,可以使用 C++ STL 中的 vector,而不是定义数组。

样例数据分析

从原始数据中,我们知道 n=3,m=3。

第一次操作为:x=1,c=2,也就是将坐标为 1 的数据加上 2,那么该点对应的数据变为 2。

第二次操作为:x=3,c=6,也就是将坐标为 3 的数据加上 6,那么该点对应的数据变为 6。

第三次操作为:x=7,c=5,也就是将坐标为 7 的数据加上 5,那么该点对应的数据变为 5。

经过 3 此操作后,我们可以得到当前数据如下图所示。

接下来,我们要进行 m 次区间和操作。

第一次区间为:l=1,r=3。如上图所示,自然对应区间和为 2+6=8。

第二次区间为:l=4,r=6。如上图所示,自然对应区间和为 0。

第三次区间为:l=7,r=8。如上图所示,自然对应区间和为 5。

算法思路

通过上面的分析,我们知道整个操作分两部分,第一部分是加法操作(将某一位置 x 上的数加 c);第二部分是查询操作(询问区间 [l, r] 的区间和)。因此整个算法的基本思路如下:

1、首先读入所有操作数据。

2、离散化数据。将 n 次操作的坐标,m 次查询的 [l, r] 进行离散化。

3、将离散化后的数据进行 n 次加法运算。

4、求离散化后数据前缀和。

5、将 m 次查询的区间和输出。注意 l 和 r 都需要对应到离散化数据。

注意:所有的数据都需要进行离散化。

为了加深对算法的理解,我们利用样例输入,写出整个数据的变化过程。

加入到 vector 后的数据如下,注意将所有的坐标加入到 vector 中。

1 3 7 1 3 4 6 7 8

排序后的数据变为

1 1 3 3 4 6 7 7 8

去重后的数据变为

1 3 4 6 7 8

加法操作后,离散化后数据变为

0 2 6 0 0 5

离散化后前缀和为

0 2 8 8 8 13

第一次查询区间为:l=1,r=3。对应的离散化区间为 [1, 2],自然对应区间和为 8-0=8。

第二次查询区间为:l=4,r=6。对应的离散化区间为 [3, 4],自然对应区间和为 8-8=0。

第三次查询区间为:l=7,r=8。对应的离散化区间为 [5, 6],自然对应区间和为 13-8=5。

AC 参考代码

STL 版本

需要使用到 STL 的 vector、pair 数据类型,以及算法库中的 sort()、unique(),vector 的 earse()。

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 3e5+4;
int lsh[MAXN];//离散化后的数据
int sum[MAXN];//前缀和

int main() {
    int n,m,i;
    cin >> n >> m;

    vector<int> nums;//所有需要离散化数据
    vector<pair<int, int> > add;//加法操作
    vector<pair<int, int> > query;//查询操作
    //读入所有加法操作
    for (i=0; i<n; i++) {
        int x, c;
        cin >> x >> c;
        add.push_back({x, c});
        nums.push_back(x);//x需要离散化
    }
    //读入查询操作
    for (i=0; i<m; i++) {
        int l,r;
        cin >> l >> r;
        query.push_back({l, r});
        nums.push_back(l);
        nums.push_back(r);
    }

    //排序
    sort(nums.begin(), nums.end());
    //去重
    nums.erase(unique(nums.begin(), nums.end()), nums.end());

    //处理加法
    for (auto item:add) {
        int x = lower_bound(nums.begin(), nums.end(), item.first)-nums.begin()+1;
        lsh[x]+=item.second;
    }

    //求前缀和
    for (i=1; i<=nums.size(); i++) {
        sum[i]=sum[i-1]+lsh[i];
    }

    //查询前缀和
    for (auto item:query) {
        int l = lower_bound(nums.begin(), nums.end(), item.first)-nums.begin()+1;
        int r = lower_bound(nums.begin(), nums.end(), item.second)-nums.begin()+1;
        cout << sum[r]-sum[l-1] << endl;
    }

    return 0;
}

特别注意:由于使用到了前缀和,数组要下标从 1 开始计算,因此使用 STL 的 lower_bound() 查找二分查找左边界的时候,需要将返回值增加 1。

发布了203 篇原创文章 · 获赞 101 · 访问量 104万+

猜你喜欢

转载自blog.csdn.net/justidle/article/details/104546705