离散化——Acwing.802区间和

离散化

定义

离散化可以简单理解为将连续的数值或数据转换为离散的、有限个不同的值或类别。离散化就是将一个可能具有无限多个取值或在一个较大范围内连续取值的变量,通过某种规则或方法,划分成若干个离散的区间或类别,并将原始数据映射到这些离散的类别中。

主要目的通常是为了简化数据处理、降低数据维度、提高计算效率或适应特定的算法和模型要求。离散化可以去除一些不太重要的细节信息,突出数据的主要特征和模式。

运用情况

当数据范围很大,但实际涉及到的不同值相对较少时,通过离散化可以用较小的索引或标记来代表原来的数值,从而节省空间和提高处理效率。

例如,有一组数值可能非常大(比如从 1 到 1000000),但实际上只有少数几种不同的值频繁出现,通过离散化可以将这些值映射到一个较小的范围内(比如 1 到 10)。

离散化的一个常见做法是先对原始数据进行排序,然后为每个不同的值分配一个唯一的编号或索引。这样在后续的处理中就可以用这些编号来代替具体的数值进行操作。

离散化在一些算法中,如一些基于区间或计数的问题中经常被用到,可以使问题的处理更加简洁和高效。

注意事项

  1. 要确保离散化后的数据能准确反映原始数据的关键特征和关系,不能丢失重要信息。
  2. 注意边界情况的处理,避免出现遗漏或错误分类。
  3. 对于不同的应用场景,选择合适的离散化方法,考虑数据分布特点和后续算法的需求。

离散方法

  1. 等宽离散化:将数据的取值范围划分为若干个等宽的区间,每个区间对应一个离散值。
  2. 等频离散化:将数据划分为若干个区间,使得每个区间内的数据数量大致相等。
  3. 基于聚类的离散化:利用聚类算法将数据聚成若干类,然后将这些类作为离散化后的类别。
  4. 基于特定规则的离散化:根据具体问题和业务需求,人为设定一些规则来进行离散化,比如根据某个阈值进行划分。
  5. 基于决策树的离散化:通过构建决策树,根据节点分裂的情况来确定离散化的划分点。

解题思路

  1. 分析问题:确定是否适合使用离散化,以及离散化的目的是什么。
  2. 选择方法:根据数据特点和问题需求选择合适的离散化方法,如等宽、等频等。
  3. 处理数据:按照选定的方法对数据进行离散化操作,建立映射关系。
  4. 验证效果:检查离散化后的数据在后续处理或分析中是否达到预期效果,必要时进行调整。
  5. 结合算法:考虑离散化后如何与具体的算法或模型相结合,使其更好地发挥作用。

例如,在处理一些区间相关的问题时,可能先对区间的端点进行离散化,然后根据离散化后的索引进行计算和处理;或者在数据量很大但实际不同值有限的情况下,通过离散化来提高存储和计算效率。同时,在解题过程中要不断思考如何优化离散化的过程以及更好地利用离散化后的结果。

Acwing.802区间和

题目描述

802. 区间和 - AcWing题库

运行代码

#include<bits/stdc++.h>  
using namespace std;  
typedef pair<int, int> PII;  
const int N = 300010;  
PII add[N];  
PII query[N];  
int a[N], s[N];  
vector<int> alls;  
int find(int x) {  
    return lower_bound(alls.begin(), alls.end(), x) - alls.begin() + 1;  
}  

int main() {  
    int n, m;  
    cin >> n >> m;  
    // 读取修改操作并存储到alls中  
    for (int i = 0; i < n; i++) {  
        cin >> add[i].first >> add[i].second;  
        alls.push_back(add[i].first);  
    }  
    // 读取查询操作并存储到alls中  
    for (int i = 0; i < m; i++) {  
        cin >> query[i].first >> query[i].second;  
        alls.push_back(query[i].first);  
        alls.push_back(query[i].second);  
    }  
    // 离散化  
    sort(alls.begin(), alls.end());  
    alls.erase(unique(alls.begin(), alls.end()), alls.end());  
    // 根据离散化后的位置更新a数组  
    for (int i = 0; i < n; i++) {  
        int pos = find(add[i].first);  
        a[pos] += add[i].second;  
    }  
    // 计算前缀和  
    for (int i = 1; i <= alls.size(); i++) {  
        s[i] = s[i - 1] + a[i];  
    }  
    // 处理查询  
    for (int i = 0; i < m; i++) {  
        int l = find(query[i].first);  
        int r = find(query[i].second);  
        cout << s[r] - s[l - 1] << endl;  
    }  
    return 0;  
}

代码思路

  1. 读取输入
    • 读取修改操作的个数 n 和查询操作的个数 m
    • 读取每个修改操作,包括修改的位置 x 和增加的值 c,并将位置 x 存储在 add 数组中。
    • 读取每个查询操作,包括查询的左边界 l 和右边界 r,并将它们存储在 query 数组中。
  2. 离散化:将所有修改和查询中涉及到的位置(即 add 和 query 数组中的 xl 和 r)存储到 alls 向量中。对 alls 向量进行排序,并使用 unique 函数去除重复元素,实现离散化。
  3. 更新数组:遍历 add 数组,对于每个修改操作,使用 find 函数找到离散化后的位置 pos,并更新 a[pos] 数组的值。
  4. 计算前缀和:遍历离散化后的位置数组 alls(从索引 1 开始,因为索引 0 不代表任何位置),计算 a 数组的前缀和,并将结果存储在 s 数组中。
  5. 处理查询
    • 遍历 query 数组,对于每个查询操作,使用 find 函数找到离散化后的左边界 l 和右边界 r 的位置。
    • 输出区间和 s[r] - s[l - 1],其中 s[r] 是右边界的前缀和,s[l - 1] 是左边界左边一个位置的前缀和(用于排除左边界本身)。
  6. 输出结果:对于每个查询操作,输出计算得到的区间和。

这个代码的时间复杂度主要由离散化、更新数组、计算前缀和以及处理查询四部分组成。其中,离散化和计算前缀和的时间复杂度都是 O(n log n),其中 n 是修改和查询操作的总数(因为 alls 的大小可能达到这个数量级)。更新数组和处理查询的时间复杂度都是 O(n),所以整体的时间复杂度是 O(n log n)。

改进思路

  1. 减少alls数组中的冗余元素:在将位置加入alls数组时,如果两个修改或查询操作的位置相同,我们只需要存储一个。这可以通过在加入新位置之前检查alls数组的末尾元素是否与新位置相同来实现。

  2. 使用std::vectorback_inserter:在将位置加入alls数组时,使用std::back_inserter可以避免显式地调用push_back,这可能会稍微提高性能。

  3. 使用std::lower_bound的返回值直接作为索引:在find函数中,我们可以直接使用std::lower_bound返回的迭代器减去alls.begin()得到离散化后的索引,而不是将其转换为整数。

改进代码(AI)

#include<bits/stdc++.h>  
using namespace std;  
typedef pair<int, int> PII;  
const int N = 300010;  
  
PII add[N];  
PII query[N];  
int a[N], s[N];  
vector<int> alls;  
  
int find(int x) {  
    return lower_bound(alls.begin(), alls.end(), x) - alls.begin() + 1;  
}  
  
int main() {  
    int n, m;  
    cin >> n >> m;  
  
    // 读取修改操作  
    for (int i = 0; i < n; i++) {  
        cin >> add[i].first >> add[i].second;  
        if (alls.empty() || alls.back() != add[i].first) alls.push_back(add[i].first);  
    }  
  
    // 读取查询操作  
    for (int i = 0; i < m; i++) {  
        cin >> query[i].first >> query[i].second;  
        if (alls.empty() || alls.back() != query[i].first) alls.push_back(query[i].first);  
        if (alls.empty() || alls.back() != query[i].second) alls.push_back(query[i].second);  
    }  
  
    // 离散化  
    sort(alls.begin(), alls.end());  
    alls.erase(unique(alls.begin(), alls.end()), alls.end());  
  
    // 根据离散化后的位置更新a数组  
    for (int i = 0; i < n; i++) {  
        int pos = find(add[i].first);  
        a[pos] += add[i].second;  
    }  
  
    // 计算前缀和  
    for (int i = 1; i <= alls.size(); i++) {  
        s[i] = s[i - 1] + a[i];  
    }  
  
    // 处理查询  
    for (int i = 0; i < m; i++) {  
        int l = find(query[i].first);  
        int r = find(query[i].second);  
        cout << s[r] - s[l - 1] << endl;  
    }  
  
    return 0;  
}

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值