AcWing学习——离散化

70 阅读4分钟

1. 概念

离散化是一种将无限空间中的有限个体,映射到有限空间中的方法,并且保持它们原有的相对顺序。

离散化只关心空间中数据的大小关系,并不关心数据的具体值。将一组分布稀疏、范围很大的原始数据,压缩成一组连续的、紧密的新数据,从而降低算法的空间和时间复杂度。

2. 思路

当给定一个无限大的元素空间中,选取有限个元素进行存放在一个新空间中。这个时候可以使用离散化进行存储,保持有限个元素之间的顺序不变,存储在新空间中。

或者当给定一个值域非常大的数组,而数组元素个数相对值域非常小,这个时候可以使用离散化,使其存储在有限的空间中。

例如:数组a {1, 3, 100, 2000, 500000},我们可以将其依次存储在[0, 1, 2, 3, 4]上。

3. 步骤

  • 数组a中可能会存在重复元素,因此我们首先需要对a数组进行去重操作。
  • 求出x离散化后的值。由于a是有序的,因此相当于求x的值在a中的下标是多少。

4. 扩展

4.1. unique(alls.begin(), alls.end())

将alls数组中所有元素进行去重,将所有不重复的元素放到最前面,并且返回去重之后新数组的尾端点。

4.2. alls.erase(alls.begin(), alls.end())

删除alls.begin()alls.end()中间的所有元素。

4.3. alls.erase(unique(alls.begin(), alls.end()), alls.end())

实现对alls去重并且删掉多余元素。

4.4. unique()实现:

原数组a:1 1 2 2 2 3 4 5 5 5 5 6

去重后的数组a:1 2 3 4 5 6 4 5 5 5 5 6

vector<int>::iterator unique(vector<int> &a)
{
    int j = 0;
    for(int i = 0; i < a.size(); i++)
        if(!i || a[i] != a[i-1])
            a[j++] = a[i];
            
    return a.begin() + j;
}

5. 代码模板

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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 + 1; // 映射到1, 2, ...n
}

6. 例题

6.1. 802. 区间和 - AcWing题库

这道题首先想到的就是前缀和+差分进行解题,但是这道题中元素下标非常大,最大可达到10^9,最小可以达到-10^9。

像是这种数据范围非常大,但是实际有意义的个数却很少,因此我们可以使用离散化进行解题。

这道题中,n和m的范围都是10^5,因此我们能够用到的下标最多只有n+2m个,也就是只会使用到3x10^5个坐标。共n个元素,m个l下标和r下标。

首先先求x离散化对应的下标k,对x+c也就相当于对a[k]+c;下标l和r也同理,将它们离散化成对应的kl和kr,然后求a[kl]到a[kr]的和即可。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 将所有操作读入
typedef pair<int, int> PII;

const int N = 300010;

int n, m;
// a是存的数组,s是前缀和
int a[N], s[N];

// alls是存的所有需要离散化的值,以及所有需要操作的下标
vector<int> alls;
// 所有插入操作用add来存,求和操作区间用query存
vector<PII> add, query;

// 求x离散化后的结果
// 将所有数映射到从1开始的自然数
// 后续操作使用到了前缀和,因此使用从1开始的数组更加方便
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 + 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);
    }
    
    // 对alls排序,去重
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());
    
    // 处理插入
    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);
        int r = find(item.second);
        cout << s[r] - s[l-1] << endl;
    }
    
    return 0;
}