离散化模板

228 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

概念

image.png

  1. 值域比较大,但是个数比较少(稀疏)
  2. 将离散的数映射到从零开始的连续自然数
  3. 映射的过程就叫离散化

离散化存在的问题

  1. a数组里面可能有重复的元素
    1. 解决方法:去重
    2. 在cpp中通常用库函数来进行去重
      1. image.png
      2. unique函数,返回去重后的队尾元素的位置
      3. 上面利用库函数的写法,完成排序+去重
  2. 如何算出a[i]离散后的值(也就是在a数组中的下标是多少)
    1. 解决方法:二分
      1. image.png

题目

www.acwing.com/problem/con… image.png

分析

  1. 如果数据范围是10^5:用前缀和也可以做
  2. 但是这里的范围太了10^9,而且数比较稀疏,所以我们选择离散化
  3. 将区间[L, R]之间的所有数映射到连续的下标从1开始的数组里面
  4. 映射完以后,数据的范围就变成了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函数是怎么实现的呢?

  1. 基本实现思路:双指针算法
    1. i指针遍历所有的数
    2. j 指针记录以下当前存到了第几个不同的数
  2. 函数满足以下性质
    1. a[i]是第一个
    2. a[i] != a[i - 1]
  3. 只要将所有满足上面两个性质的数拿出来,就是先了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;
}