离散化的应用
这里的离散化的应用特指整数的离散化、有序的离散化、饱序的离散化。
如:
一个很大的数组但是只用到其中的一小部分元素,此时就可以用离散化来解决此问题。
这里来看一道acwing上的一道模板题:
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。
输入格式:
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。
输出格式:
共 m 行,每行输出一个询问中所求的区间内数字和。
数据范围:
−10 ^9 ≤ x ≤ 10 ^9,
1 ≤ n , m ≤ 10 ^5,
−10 ^9 ≤ l ≤ r ≤ 10 ^9,
−10000 ≤ c ≤ 10000
先上代码:
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int, int>PII;
const int N = 300010;
int a[N], s[N];
vector<PII>add, query;
vector<int>all;
int find(int x)
{
int l = 0; int r = all.size() - 1;
while (l < r)
{
int mid = r + l >> 1;
if (all[mid] >= x)r = mid;
else l = mid + 1;
}
return l + 1;
}
int main()
{
int n, m, x, c, l, r;
cin >> n >> m;
for (int i = 0; i < n; i++)
{
cin >> x >> c;
add.push_back({ x,c });
all.push_back(x);
}
for (int i = 0; i < m; i++)
{
cin >> l >> r;
query.push_back({ l,r });
all.push_back(l);
all.push_back(r);
}
sort(all.begin(), all.end());
all.erase(unique(all.begin(), all.end()), all.end());
for (auto b : add)a[find(b.first)] += b.second;
for (int i = 1; i <= all.size(); i++)s[i] = s[i - 1] + a[i];
for (auto b : query)cout << s[find(b.second)] - s[find(b.first)-1] << endl;
return 0;
}
以下是对代码的解析:
看题意可知一共是有2 * 10 ^9的数据
但是由输入的情况可知最多会用到3 * 10 ^3
为什么这么说:
一般求区间和是应用前缀和的算法,但是这里的因为数据过大直接应用前缀和肯定是行不通的。但是实际用到的数据最多有3 * 10^3,最多用到的数据来自拿里: 1 * 10 ^ 3个不同的需要相加的位置,1 * 10 ^ 3次方个没用过位置且不同位置的 l (区间左值) ,1 * 10 ^ 3次方个不同位置且没用过的 r (区间右值),因此2 * 10 ^9个位置(数组下标)最多用3 * 10 ^3个位置(数组下标)。
所以很大的数据但是只会用到一小部分,且区间上元素的位置都是有序的,则完全可以应用离散化的思想。
离散化的应用需要:先排序,再删除重复元素,最后就是索引元素离散化后对应的值。
针对这一题求区间和给出的数据位置范围大但是用到的数据位置少且区间有序,则做法分为以下几步:
- 将所有用到的需要加数的位置的下标收集到待离散化的数组中
- 将所有用到的区间左右位置的下标收集到待离散化的数组中
- 将待离散化的数据先排序,再去重。
- 为什么要先排序,为了应用unique函数将待离散化的数据的重复数据筛选出来,然后删除掉,为了后面有序数组的数据离散化(二分查找)。
- 为什么要去重,因为离散化数据的时候用到了二分查找,将找到的数据返回其数组下标成功完成了数据离散化。
- 将数据离散化后就可以安心的进行前缀和算法了,因为数据范围变小,所以直接用前缀和公式求取区间和。二分和前缀和具体思想请看往期的博客。