离散化思维的应用

离散化思维的应用

毕业前最后的燃烧。

离散化思维

当题目中数据的范围很大但是数据的总量不是很大。此时如果需要用数据的值来映射数组的下标时,就可以用离散化的思想先预处理一下所有的数据,使得每一个数据都映射成一个较小的值。之后再离散化之后的数去处理问题。

比如: [ 99 , 9 , 404 , 1145141919810 ] [99, 9, 404, 1145141919810] [99,9,404,1145141919810] 离散之后就变成 [ 2 , 1 , 3 , 4 ] [2, 1, 3, 4] [2,1,3,4]。

有的题若不使用离散化处理的话,数据量是 10 9 10^9 109 将开不了这么大的数组,此时将束手无策。

案例:给定 n n n 个数,离散化之后,输出每一个数离散化之后的值。

测试数据:

复制代码
10
1999999
12
1999999
-48444
568
12
-100
-2845630
100000001
263

离散化思路:排序+去重+二分查找

首先对这些数据进行排序,然后去重,最后通过二分查找数的索引,此时它们的下标索引组成的集合即为它们离散化后的值。

参考程序:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
int n;
vector<int> a, // 原始数据
    disc;      // 离散化结果

// 二分寻找左边界,查找x的位置
int find(int x) {
    int l = 1, r = disc.size();
    while (l < r) {
        int mid = (l + r) / 2;
        if (disc[mid] >= x)
            r = mid;
        else
            l = mid + 1;
    }
    return l;
}

int main() {
    cin >> n;
    a.resize(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        disc.push_back(a[i]);
    }
    // 离散化
    sort(disc.begin() + 1, disc.end());   // 排序
    unique(disc.begin() + 1, disc.end()); // 用STL工具去重
    for (int i = 1; i <= n; i++)
        printf("%10d", a[i]);
    cout << "\n";
    for (int i = 1; i <= n; i++)
        printf("%10d", find(a[i]));
    cout << "\n";
    return 0;
}

将输出整理成表格,可看到每个数在这个集合中都有指定的索引。

|------|---------|----|---------|--------|-----|----|------|----------|-----------|-----|
| 离散化前 | 1999999 | 12 | 1999999 | -48444 | 568 | 12 | -100 | -2845630 | 100000001 | 263 |
| 离散化后 | 7 | 4 | 7 | 2 | 6 | 4 | 3 | 1 | 8 | 5 |

离散化思路:排序+哈希

哈希表和红黑树自带去重,红黑树还自带排序。但红黑树的查询效率大多数情况低于哈希,所以这个离散化思路的主角是哈希,但缺少的有序需要排序来弥补。

这里的哈希在 C++ 中可用 unordered_mapunordered_set 表示,红黑树在 C++ 中可用 mapset 表示。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
vector<int> a;               // 原始数据
vector<int> disc;            // 经过排序的原始数据
unordered_map<int, int> ump; // <原始数据, 离散化结果>
int main() {
    cin >> n;
    a.resize(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        disc.push_back(a[i]); // 数据准备阶段
    }
    // 离散化
    sort(disc.begin(), disc.end()); // 排序
    for (int i = 0, cnt = 0; i < disc.size(); i++) {
        if (ump.count(disc[i])) // 去重
            continue;
        ump[disc[i]] = ++cnt; // 当前这个值是第几号元素
    }
    // 离散化结果展示或其他处理
    for (int i = 1; i <= n; i++)
        printf("%10d", a[i]);
    cout << "\n";
    for (int i = 1; i <= n; i++)
        printf("%10d", ump[a[i]]);
    cout << "\n";
    return 0;
}

离散化是一种处理数据的技巧,模版不固定,根据算法思想就可以实现。并且实现离散化的方式也可以在原有模板的基础上修改。

P1496 火烧赤壁 - 洛谷

P1496 火烧赤壁 - 洛谷

贪心算法:区间问题

区间问题。

首先是贪心策略:对每个区间的左端点进行排序。然后合并互相重叠的区间,直至无法合并时,再将区间长度进行统计。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

struct Area {
    int x;
    int y;
    bool operator<(const Area &aim) const {
        return x < aim.x;
    }
};
vector<Area> num;

int main() {
    int n, ans = 0;
    cin >> n;
    num.resize(n + 1, {0, 0});
    for (int i = 1; i <= n; i++)
        cin >> num[i].x >> num[i].y;
    sort(num.begin() + 1, num.end()); // 区间按左端点排序
    for (int id = 1; id <= n; ++id) { // 合并区间
        int x = num[id].x, y = num[id].y;
        while (id + 1 <= n && y > num[id + 1].x) {
            y = max(y, num[id + 1].y); // 更新重叠区间的右端点
            id++;
        }
        ans += abs(y - x);
    }
    cout << ans;
    return 0;
}

离散化处理+差分

若没有数据量的限制,还可以用差分算法统一修改区间内的元素的值。但既然有,想用差分解决,就需要先离散化。

首先可将所有的区间并排放在一起,例如本题的测试样例,可创建数组 disc[]={-1,1,2,5,9,11} ,求出它们的离散化结果:ump[]={1,2,3,4,5,6}umpunordered_map 的简写。

题意问的是着火区域的长度,对每个元素来说,着火不着火无非就是一种状态。所以可维护一个全 0 的数组,然后根据已有区间信息进行差分运算,然后还原。例如对测试样例维护一个差分数组,然后枚举区间进行差分操作,整个表格如下:

还原 f 1 0 1 2 1 0 差分后 f 1 − 1 1 1 − 1 − 1 差分操作 + 1 − 1 + 1 + 1 − 1 − 1 差分前 f 0 0 0 0 0 0 离散化结果 u m p 1 2 3 4 5 6 原始数据 d i s c − 1 1 2 5 9 11 \begin{array}{|c|c|c|c|c|c|c|}\hline还原f&1&0&1&2&1&0\\\hline差分后f&1&-1&1&1&-1&-1\\\hline差分操作&+1&-1&+1&+1&-1&-1\\\hline差分前f&0&0&0&0&0&0\\\hline离散化结果ump&1&2&3&4&5&6\\\hline原始数据disc&-1&1&2&5&9&11\\\hline\end{array} 还原f差分后f差分操作差分前f离散化结果ump原始数据disc11+101−10−1−102111+103221+10451−1−10590−1−10611

然后根据还原后的结果,凡是大于 0 的区域,都可根据离散化结果之前的原始数据进行长度计算。例如这里,第1段大于0的是 [1,2) ,第 2 段大于 0 的是 [3,6) , 2 段的长度总和是 d i s c [ 2 ] − d i s c [ 1 ] + d i s c [ 6 ] − d i s c [ 3 ] = 1 − ( − 1 ) + 11 − 2 = 11 disc[2]-disc[1]+disc[6]-disc[3]=1-(-1)+11-2=11 disc[2]−disc[1]+disc[6]−disc[3]=1−(−1)+11−2=11 , d i s c disc disc 表示根据离散化结果查询原始数据。

按照这个思路也能解决这个题。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
int n;
int a[N], b[N]; // 区间的原始数据
int pos;
int disc[N * 2]; // 离散化前的原始数据
unordered_map<int, int> ump;
int f[N * 2]; // 差分数组载体
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i] >> b[i];
        disc[++pos] = a[i];
        disc[++pos] = b[i];
    }
    // 离散化操作
    sort(disc + 1, disc + 1 + pos);                      // 排序
    pos = unique(disc + 1, disc + 1 + pos) - (disc + 1); // 去重
    for (int i = 1; i <= pos; i++) // 插入哈希表建立映射关系
        ump[disc[i]] = i;

    // 差分操作
    for (int i = 1; i <= n; i++) {
        int l = ump[a[i]], r = ump[b[i]];
        f[l]++;
        f[r]--;
    }
    // 差分数组还原
    for (int i = 1; i <= pos; i++)
        f[i] += f[i - 1];
    // 根据大于0的区域获得区间长度
    int ans = 0;
    for (int i = 1; i <= pos; i++) {
        int j = i;
        while (j <= pos && f[j] > 0)
            j++;
        ans += disc[j] - disc[i]; // 区间的长度是用原始数据相减
        i = j;                    // 跳过处理过的区间
    }
    cout << ans << endl;
    return 0;
}

P3740 贴海报 - 洛谷 区间覆盖情况

P3740 [HAOI2014\] 贴海报 - 洛谷](https://www.luogu.com.cn/problem/P3740) 读题:贴海报在一面墙上,新海报可以覆盖旧海报,问人能看到的海报数。 首先能想到的解法是模拟整个流程:根据海报的位置将指定区域内的数组用指定的值覆盖表示贴海报,贴完所有海报后统计不同的数字个数即为答案。时间复杂度 O ( n m ) \\text{O}(nm) O(nm) ,数据量来到 10 10 10\^{10} 1010 ,但时间限制只有 1 秒,所以不能用这个方式。 虽然墙的长度很长,但区间个数很少,此时就可以将将所有区间的数字收集起来进行离散化处理,然后根据区间对数组进行赋值覆盖处理,再统计不同的数的个数。但这个思路有严重的问题: 对这个样例: 6 3 2 5 2 3 5 6 离散化后的结果: 2 3 5 6 1 2 3 4 若是正常模拟,可以看到 3 张海报: ![请添加图片描述](https://i-blog.csdnimg.cn/direct/389bea89bdd14be38bd0fc41779a445f.png) 但离散化之后,区间实际上是缩小了的,所以 `[3,5]` 会被缩小,使得这个区域的海报不可见。这里的解决方式是在将 ( x , y ) (x,y) (x,y) 进行离散化处理时,顺便将 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1) 也进行离散化处理。 但这个思路也不完全适用所有情况,需要实际分析。 ```cpp #include using namespace std; struct Area { int x; int y; }; int n, m; vector area; vector disc; // 离散化前的原始数据 unordered_map ump; vector wall; int main() { cin >> n >> m; area.resize(m + 1, {0, 0}); wall.resize(4 * n + 1, 0); for (int i = 1; i <= m; i++) { cin >> area[i].x >> area[i].y; disc.push_back(area[i].x); disc.push_back(area[i].x + 1); // 见上文分析 disc.push_back(area[i].y); disc.push_back(area[i].y + 1); } // 离散化处理 sort(disc.begin(), disc.end()); for (int i = 0, cnt = 0; i < disc.size(); i++) { auto x = disc[i]; if (ump.count(x) != 0) continue; ump[x] = ++cnt; } // 贴海报 for (int i = 1; i <= m; i++) { int s = ump[area[i].x], e = ump[area[i].y]; for (; s <= e; s++) wall[s] = i; } // 统计 set st; // 利用红黑树去重的性质进行统计 for (int i = 1; i < wall.size(); i++) st.insert(wall[i]); cout << st.size() - 1; // 多插入了一个0 return 0; } ``` ## OJ参考 [P1496 火烧赤壁 - 洛谷](https://www.luogu.com.cn/problem/P1496) \[P3740 [HAOI2014\] 贴海报 - 洛谷](https://www.luogu.com.cn/problem/P3740)

相关推荐
机器视觉的发动机1 小时前
图像处理-机器视觉算法中的数学基础
开发语言·人工智能·算法·决策树·机器学习·视觉检测·机器视觉
健忘的派大星3 小时前
需求激增800%!2025年第一硬通货:懂大模型、云计算和硬件的“前沿部署工程师”!
人工智能·算法·架构·langchain·云计算·大模型学习·大模型教程
PAK向日葵4 小时前
【C++】整数类型(Integer Types)避雷指南与正确使用姿势
c++·安全·面试
代码改善世界9 小时前
【数据结构与算法】栈和队列题解
数据结构
ShineWinsu10 小时前
对于C++:继承的解析—上
开发语言·数据结构·c++·算法·面试·笔试·继承
pp起床10 小时前
动态规划 | part05
算法·动态规划
GuangHeAI_ATing10 小时前
国密算法SSD怎么选?这3款国产固态硬盘安全又高速
算法
左手厨刀右手茼蒿10 小时前
Flutter for OpenHarmony: Flutter 三方库 hashlib 为鸿蒙应用提供军用级加密哈希算法支持(安全数据完整性卫士)
安全·flutter·华为·c#·哈希算法·linq·harmonyos
雨泪丶10 小时前
代码随想录算法训练营-Day34
算法