题目来源:AcWing 802. 区间和
一、题目描述
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0 0 0。
现在,我们首先进行 n n n 次操作,每次操作将某一位置 x x x 上的数加 c c c。
接下来,进行 m m m 次询问,每个询问包含两个整数 l l l 和 r r r,你需要求出在区间 [ l , r ] [l,r] [l,r] 之间的所有数的和。
输入格式
第一行包含两个整数
n
n
n 和
m
m
m。
接下来 n n n 行,每行包含两个整数 x x x 和 c c c。
再接下来 m m m 行,每行包含两个整数 l l l 和 r r r。
输出格式
共
m
m
m 行,每行输出一个询问中所求的区间内数字和。
数据范围
−
1
0
9
≤
x
≤
1
0
9
,
−10^9≤x≤10^9,
−109≤x≤109,
1
≤
n
,
m
≤
1
0
5
,
1≤n,m≤10^5,
1≤n,m≤105,
−
1
0
9
≤
l
≤
r
≤
1
0
9
,
−10^9≤l≤r≤10^9,
−109≤l≤r≤109,
−
10000
≤
c
≤
10000
−10000≤c≤10000
−10000≤c≤10000
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5
二、离散化算法思路
离散化定义
一般而言,我们需要将一系列数作为数组下标进行某些操作时,如果这些数据值域较小,可以直接开一个数组使用。但是有些情况下这一些列数的值域很大如 1 0 9 10^9 109 ,但是这一些列数的数据量很少如 1 0 5 10^5 105。如果放在整个数轴上看,就会显得“在很大的区间上,所有点都非常稀疏”这种利用率很低的情况;而且因为数据的值域很大,数组开不了这么大。
离散化的流程
- 将所有可能用到的待离散化数据放入向量 a l l s alls alls 中,注意:可能用到的数据既包含真实存在的数据,也包含查询操作涉及的数据。
for (auto e : operators, querys ...) alls.push_back(e);
- 将 a l l s alls alls 向量排序,并且进行去重操作。
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
// unique()可以将有序序列重复的部分放到序列最后,并且返回重复部分起点的位置
- 使用二分查找,建立 a l l s [ 0 , 1 , 2 , . . . ] alls[0, 1, 2, ... ] alls[0,1,2,...] 与 0 , 1 , 2 , . . . 0, 1, 2, ... 0,1,2,... 之间的映射关系,这种方式速度最快。
// 建立映射关系
int find(int x)
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r;
}
...
// 使用离散化以后的值
find(alls[i])
...
备选方案:使用hashmap实现find()操作,这种方式实现简单,但是速度比二分查找慢几乎一倍。
unordered_map<int, int> book;
// 建立映射关系
for (int i = 0; i < alls.size(); i++) book[alls[i]] = i;
...
// 使用离散化以后的值
book[alls[i]];
...
三、代码
二分映射
此方法实测10个数据点耗时599 ms,速度最快。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> node;
const int N = 3e5 + 10; // 基础1e5,查询2e5
vector<int> alls; // 存储所有的待离散化数
vector<node> add, query;
int s[N]; // 前缀和
int n, m;
// 建立离散化映射
int find(int x)
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return l + 1; // 因为前缀和要求起点为1,因此+1
}
int main()
{
scanf("%d%d", &n, &m);
// 存储加操作涉及的坐标
for (int i = 0; i < n; i++)
{
int x, c;
scanf("%d%d", &x, &c);
add.push_back({x, c});
alls.push_back(x);
}
// 存储查询操作涉及的坐标
for (int i = 0; i < m; i++)
{
int l, r;
scanf("%d%d", &l, &r);
query.push_back({l, r});
alls.push_back(l), alls.push_back(r);
}
// 离散化
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
// 处理加操作
for (int i = 0; i < n; i++) s[find(add[i].first)] += add[i].second;
// 处理前缀和
for (int i = 1; i <= alls.size(); i++) s[i] += s[i - 1];
// 处理查询操作
for (int i = 0; i < m; i++)
{
int l = find(query[i].first), r = find(query[i].second);
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}
hash映射
此方法代码较为简单,但是因为hashmap常数较大,因此实际10个测试数据运行时间1145 ms,速度较慢。
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
typedef pair<int, int> node;
const int N = 3e5 + 10; // 基础1e5,查询2e5
vector<int> alls; // 存储所有的待离散化数
vector<node> add, query;
unordered_map<int, int> book;
int s[N]; // 前缀和
int n, m;
int main()
{
scanf("%d%d", &n, &m);
// 存储加操作涉及的坐标
for (int i = 0; i < n; i++)
{
int x, c;
scanf("%d%d", &x, &c);
add.push_back({x, c});
alls.push_back(x);
}
// 存储查询操作涉及的坐标
for (int i = 0; i < m; i++)
{
int l, r;
scanf("%d%d", &l, &r);
query.push_back({l, r});
alls.push_back(l), alls.push_back(r);
}
// 离散化
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
// 这里用hashset代替二分查找
for (int i = 0; i < alls.size(); i++) book[alls[i]] = i + 1;
// 处理加操作
for (int i = 0; i < n; i++) s[book[add[i].first]] += add[i].second;
// 处理前缀和
for (int i = 1; i <= alls.size(); i++) s[i] += s[i - 1];
// 处理查询操作
for (int i = 0; i < m; i++)
{
int l = book[query[i].first], r = book[query[i].second];
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}