本题是一个非常好的模板题,结合了前缀和、离散化的知识点。
题目
题目链接
AcWing的OJ,https://www.acwing.com/problem/content/804/。
我的OJ,http://47.110.135.197/problem.php?id=5239。
题目描述
假定有一个无限长的数轴,数轴上每个坐标上的数都是 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。