一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
概念
- 值域比较大,但是个数比较少(稀疏)
- 将离散的数映射到从零开始的连续自然数
- 映射的过程就叫离散化
离散化存在的问题
- a数组里面可能有重复的元素
- 解决方法:去重
- 在cpp中通常用库函数来进行去重
- unique函数,返回去重后的队尾元素的位置
- 上面利用库函数的写法,完成排序+去重
- 如何算出a[i]
离散后的值(也就是在a数组中的下标是多少)- 解决方法:二分
- 解决方法:二分
题目
分析
- 如果数据范围是10^5:用前缀和也可以做
- 但是这里的范围太了10^9,而且数比较稀疏,所以我们选择离散化
- 将区间[L, R]之间的所有数映射到连续的下标从1开始的数组里面
- 映射完以后,数据的范围就变成了10^5,然后用前缀和的做法将这道题目给解决掉
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII; // 存的是每个操作
const int N = 300010; // 数据范围
int n, m;
int a[N], s[N]; // a数组里面是我们存的数,s是前缀和
vector <int> alls; // 存的所有要离散化的值
// 插入操作 和求操作
vector<PII> add, query;
// 求一下x运算后的结果,用的二分查找
int find(int x)
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = (l + r) >> 1;
// 找到大于等于x的最小的数
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; // 映射到从1开始的数组
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
{
int x, c;
cin >> x >> c; // 读入插入操作:在x的位置加入c
add.push_back({x, c});
alls.push_back(x); // 将x加入到待离散化的组里面
}
for (int i = 0; i < m; i++)
{
int l, r;
cin >> l >> r; // 读入左右区间
// 区间的左右端点也是需要离散化的
query.push_back({l, r});
// 将区间的左右端点加入到带离散化的数组里面
alls.push_back(l);
alls.push_back(r);
}
// alls数组去重
sort(alls.begin(), alls.end()); // 1. 排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 2. 将里面的重复元素去掉
// 分别处理一下两种操作
// 操作1:插入操作
for (auto item : add)
{
// 先求一下离散后的值是多少
int x = find(item.first);
a[x] += item.second; // 在离散后的值(对应数组的下标)加上我们要加的数
}
// 预处理前缀和
for (int i = 1; i <= alls.size(); i++) s[i] = s[i - 1] + a[i];
// 处理询问
for (auto item : query)
{
int l = find(item.first), r = find(item.second);
// 求和
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
uinque函数是怎么实现的呢?
- 基本实现思路:双指针算法
- i指针遍历所有的数
- j 指针记录以下当前存到了第几个不同的数
- 函数满足以下性质
- a[i]是第一个
a[i] != a[i - 1]
- 只要将所有满足上面两个性质的数拿出来,就是先了unique函数
// 返回vector<int>迭代器
vector<int>::iterator unique(vector<int> a)
{
for (int i = 0; i < a.size(); i++)
// 判断是否满足上面写的两个性质
if (!i || a[i] != a[i - 1])
a[j++] = a[i];
// 此时a[0] ~ a[j - 1] 存a中所有不同的数
return a.begin() + j;
}