【第九课】离散化(acwing-802区间和 / c++代码 / 思路超详解 )

目录

acwing-802 区间和

离散化算法思想 

本题思路 

代码如下

代码思路 

一些解释

1.pair

2.typedef 

3.二分查找

4.for (int i = 1; i <= alls.size(); i++)


 

acwing-802 区间和

离散化算法思想 

在学习离散化之前,看到这道题我直接想到的是前缀和,唯一有点问题的就是题目里面说的是一个无限长的数轴。这点也是我们学习离散化的作用。

其主要思想就是:当数值可取范围特别大(导致我们没办法开数组,会超限),但是我们实际操作的、用上的位置很少、很稀疏,这时候我们就要采用离散化的方法,把使用到的位置的值存放到一个下标从0或者从1开始的数组里面。这样大大节省存储空间,同时也提高了数据处理的效率。

本题思路 

这道题里无限长的数轴就是一个可取的很大的范围。但是我们看实际会使用到的位置是多少?

首先n次插入操作,每次会用到一个坐标,n最大是1e5;下面m次询问,每次询问我们会输入 l 和 r ,这样我们每次要查找的坐标有两个,m的范围是1e5。因此我们实际会用到的坐标只有3e5个。

于是我们创建一个存储离散化之后的数据的数组,在这个数组里进行各种操作。由于我们会使用到前缀和,所以我们这里数据与下标的映射从1开始

了解了大体思想之后,其实代码还是不太会写,我这里就根据答案的代码进行思路的解释了。感觉还是比较复杂的。

代码如下

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 3e5 + 10;
int a[N], s[N];
int n, m;

typedef pair<int, int> PII;//创建一个新的数据类型
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;
        if (alls[mid] >= x)//找第一个>=x的数的位置
            r = mid;
        else
            l = mid + 1;
    }
    return r + 1;
}
int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++)
    {
        int x, c;
        cin >> x >> c;
        add.push_back({x, c});
        //把坐标x和后来要添加的c作为一个键值对存储(捆绑在一起),此时并没有进行操作,后面for循环才执行了操作
        alls.push_back(x);//把坐标存进alls数组
    }
    for (int i = 0; i < m; i++)
    {
        int l, r;
        cin >> l >> r;
        query.push_back({l, r});//把将要计算的区间端点存放在query里,后面for循环时实现其功能
        //将用到的坐标存入alls
        alls.push_back(l);
        alls.push_back(r);
    }
    //alls去重,去掉重复出现的坐标,并排好序存入alls数组,这样alls数组的元素就是一个 使用到的坐标的连续区间了
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());

    for (auto item : add) // 实现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;
}

代码思路

我们整体的思路就是:

1.使用alls数组记录下所有提到的、使用到的数轴上的坐标。因此需要对alls数组进行排序和去重,使得alls数组的元素是被压缩后(删掉了未使用的坐标)的连续的坐标序列

2.我们创建了一个新的类型pair,存储一对键值对,包含两个int类型的相关联的数据。(后面会补充一下这个语法)

3.创建一个元素数据类型为pair的add数组,其作用是通过for循环,将原数组a的数据确定下来(处理+c的工作)

(前面这两个数组实现其功能的前提都是先读取进操作数据[push_back()])

4.得到原数组之后,按照求前缀和的操作,求得s数组

5.创建一个元素数据类型为pair的query数组,其作用是通过for循环,处理每组询问,以得到所求的区间和再输出

一些解释

1.pair

pair是一种用于存储(相关联)两个值的数据结构,这两个值可以是任何类型,不必相同。它是c++标准模板库中的一个模板类。其使用时需要引头文件  #include <utility>  。它通过 .first 和 .second来访问pair中的元素。在这道题里我们把它看作一种新的数据类型即可。关于其其他功能暂不讨论。

2.typedef 

typedef pair<int, int> PII;

这里我们使用 typedef 关键字,用于为现有的类型定义一个新的名字。这样我们就不用每次创建该类型数据的实际就写 pair<int, int> 这么一大串了,后面紧跟的 PII 就是他的新名字,直接用 PII 创建即可。使代码书写更方便简洁

3.二分查找

int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) // 找第一个>=x的数的位置
            r = mid;
        else
            l = mid + 1;
    }

其实这里我们二分实现的功能仅仅是找到一个数,而且alls数组中的坐标都是有序的且并没有重复元素。是比较基础的二分。

我之前写过基础的二分用来查找一个点,而我们后面学习的两种模板的二分适用于比较复杂的需要分开两个满足不同性质的区域。这里我们使用的是 寻找第一个>=x的位置  的模板,使用了更完善的模板,当然能够解决查找一个点这样的基础功能。

add和query的x l r 三个表示坐标的变量,在alls里二分寻找的时候,其实找的是对应元素所在的下标为1到n这连续数的 位置。最后返回目标下标+1,是为了使 a数组和 s数组的下标从1开始,因为前缀和数组需要从1开始

4.for (int i = 1; i <= alls.size(); i++)

这是对s数组前缀和的处理。我们这里以alls数组的数量为基础进行求得前缀和,是因为alls数组包含了所有使用到的坐标,并进行了压缩去重,这就保证了我们a数组和s数组最大的使用量也就是alls数组的大小了

在实际操作中,我们只会处理到alls数组的大小的位置,因为这就是所有可能的坐标值的数量。a和s数组最多位数也就只到alls的最多位,且每位元素与alls的下标一一对应也是连续存储的,但是我们a和s数组定义的大小非常大,预分配了更多的空间以处理可能的最大坐标值,所以我们进行前缀和操作的时候,以alls数组的大小为基准

5.基础二分

这里附上基础二分的算是模板代码吧。

int find(vector<int> &nums,int target)
{
    int left = 0;
    int right = nums.size() - 1;
    while (left <= right) // 循环出口是 查找范围是否为空
    {
        int mid = left + (right - left) / 2;
        if (nums[mid] > target)
        {
            right = mid - 1;//由于mid值已经判断过不是我们想要的
        }
        else if (nums[mid] < target)
        {
            left = mid + 1;
        }
        else
        {
            return mid;//一直缩小区间范围,直到mid=target
        }
    }
    return -1;
}

其实看到二分还是有点莫名发怵的[😂],有点忘记了 。想要复习的可以看之前的文章。

http://blog.csdn.net/Swillow_/article/details/131729869


好啦。关于这道题就解释完啦。有一些新用到的知识,代码还是不太好理解的,相信有了上面的梳理解释,应该可以啦。 

如果有问题欢迎指出,非常感谢!!

也欢迎交流建议奥!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值