离散化(离散化更关注数据的相对大小)

离散化是把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。

即离散化是在不改变数据相对大小的条件下,对数据进行相对的缩小。

如:对于1,999,100000.15 处理后变为 1,3,4,2

对于数据更关注相对大小时可以应用离散化。

模版一

数组离散化

对于数组离散化分为四步:

  1. 创建原数组副本
  2. 对副本数组进行排序
  3. 对排序后的副本进行去重
  4. 对离散化后的值进行匹配
for(int i = 1; i <= n; i++)
    temp[i] = arr[i]; //创建原数组副本
sort(temp + 1, temp + n + 1); //对副本元素进行排序
//unique函数对排序副本去重,并获取去重后的数组长度
int len = unique(temp + 1, temp + n + 1) - (temp +1);
for(int i = 1, i <= n; i++)
    //查询原数组各元素在副本中的下标,下标即为排名,将其作为离散化后的值
    arr[i] = lower_bound(temp + 1, temp + len + 1, arr[i]) - temp;

unique函数

unique函数主要用于去除相邻的重复元素。其不会真正删除元素,而是将不重复的元素保留在前面,并返回一个迭代器,该迭代器指向新的逻辑末尾。

注意unique只会去除相邻的重复元素,所以使用前要对元素进行排序操作。

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

int main() {
    int arr[8] = {1, 2, 2, 3, 4, 4, 4, 5};

    // 使用 unique 去除相邻的重复元素
    int* new_end = unique(arr, arr + 8);

    // 计算去重后的数组长度
    int new_size = new_end - arr;

    // 输出去重后的数组
    for (int i = 0; i < new_size; i++) {
        cout << arr[i] << endl;
    }

    return 0;
}
/*
输出结果:
1
2
3
4
5
*/

lower_bound函数

lower_bound 函数是 C++ 标准库中的一个算法,用于在已排序的范围内查找第一个不小于给定值的元素。它返回一个指向该元素的迭代器。如果没有不小于给定值的元素,则返回 end() 迭代器。

函数原型

  1. 默认比较方式
template<class ForwardIt, class T>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value);
    • first, last:指向要查找范围的迭代器。
    • value:要查找的值。
  1. 使用自定义比较函数
template<class ForwardIt, class T, class Compare>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value, Compare comp);
    • comp:自定义比较函数或谓词,用于定义元素的排序规则。

返回值

  • 返回一个指向第一个不小于 value 的元素的迭代器。
  • 如果 value 大于范围内的所有元素,则返回 last

使用示例

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

int main() {
    std::vector<int> vec = {10, 20, 30, 40, 50};

    // 查找第一个不小于25的元素
    auto it = std::lower_bound(vec.begin(), vec.end(), 25);

    if (it != vec.end()) {
        std::cout << "First element not less than 25 is " << *it << std::endl;
    } else {
        std::cout << "No element found not less than 25" << std::endl;
    }

    return 0;
}

输出结果

First element not less than 25 is 30

代码解释

  • lower_bound(vec.begin(), vec.end(), 25)vec 中查找第一个不小于 25 的元素。
  • 由于 30 是第一个不小于 25 的元素,所以 it 指向的是 30

时间复杂度

lower_bound 的时间复杂度是 O(log n),因为它使用的是二分查找算法。这使得它非常高效,特别是在处理大型有序数据集时。

注意事项

  • lower_bound 需要作用于已排序的范围,否则结果是不确定的。
  • 在返回的迭代器位置插入 value 可以保持容器的有序性。

vector离散化

vector<int> arr, temp; //定义向量arr,以及其副本temp
sort(temp.begin(), temp.end()); //对副本进行排序
//对排好序的副本进行去重操作
temp.erase(unique(temp.begin(), temp.end()), temp.end());
//获取离散化之后各变量所对应的值
for(int i = 0; i < n; i++)
    arr[i] = lower_bound(temp.begin(), temp.end(), arr[i]) - temp.begin();

erase函数

erase 函数在 C++ 标准库中的容器(如 vector, deque, list 等)中用于删除元素或删除指定范围内的元素。其主要作用是调整容器的大小,并且删除元素后容器中的其余元素会向前移动以填补空缺。

使用方式

  1. 删除单个元素
//iterator表示迭代器
iterator erase(iterator pos);

示例

std::vector<int> vec = {1, 2, 3, 4, 5};
vec.erase(vec.begin() + 2); // 删除索引为2的元素,即3
// vec 现在是 {1, 2, 4, 5}
    • pos:要删除的元素的位置。
    • 返回值:返回一个指向删除元素之后元素的迭代器。如果删除的是最后一个元素,则返回容器的 end() 迭代器。
  1. 删除指定范围的元素
iterator erase(iterator first, iterator last);

示例

std::vector<int> vec = {1, 2, 3, 4, 5};
// 删除索引范围[1, 4)内的元素,即2, 3, 4
vec.erase(vec.begin() + 1, vec.begin() + 4); 
// vec 现在是 {1, 5}
    • first:要删除的第一个元素的位置。
    • last:要删除的范围的结束迭代器(不包含该位置)。
    • 返回值:返回一个指向删除范围之后第一个元素的迭代器。如果删除的是最后一个元素或整个范围,则返回容器的 end() 迭代器。

注意事项

  • 删除操作后迭代器的有效性:在 vectordeque 中,删除元素会导致后续元素位置发生改变,这可能使得指向这些元素的迭代器失效。因此,在 erase 操作后,通常需要更新迭代器。
  • 时间复杂度:在 vectordeque 中,erase 的时间复杂度是线性的 (O(n)),因为需要移动删除元素后的所有元素。而在 list 中,erase 的时间复杂度是常数 (O(1)),因为列表是链式结构,不需要移动元素。

总结

erase 函数用于删除容器中的一个或多个元素,并调整容器的大小。它通常与 unique 函数结合使用,以删除容器中的重复元素。

二分法获取离散化后对应的值

vector<int> alls;
//利用二分法找到第一个大于等于x的位置
int find(int x){
     int l = 0, r = alls.size() - 1; //alls表示排序去重后的副本
    while(l < r){
        int mid = l + r >> 1;
        if(alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return  r + 1;//返回从1开始的映射
}
//从下标为1的位置输入数据
for(int i = 1; i <= n; i++){
    cin >> arr[i];
    temp[i] = arr[i];
}

sort(temp + 1, temp + n + 1); //对副本进行排序
//排序后去重,并获取去重后副本长度
int len = unique(temp + 1, temp + n + 1) - (temp + 1);

int find(int x) // 找到第一个大于等于x的位置
{
    int l = 1, r = len; //副本离散化操作后数据下标从1开始,所以l = 1
    while (l < r)
    {
        int mid = l + r >> 1;
        if (temp[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r; // 映射到1, 2, ...n
}

模版二

对于个别题目,有时会把相同元素根据输入顺序离散化为不同的数据。

此时利用lower_bound()函数实现略显困难,毕竟去重后的数据使用lower_bound()函数查找相同元素只会有一个返回值,则对于该问题需要改变思路:

  1. 创建原数组副本,同时记录每个元素出现的位置。
  2. 对副本进行从小到大排序,当数据相同时,按照出现顺序从小到大排序。
  3. 将离散化后的数据放回原数组。
struct Data {
  int idx, val;
//后面的const表明调用函数的成员对象不能被修改
//const Data& o的const表明对象o的成员不能被修改
  bool operator<(const Data& o) const {
    if (val == o.val)
      return idx < o.idx;  // 当值相同时,先出现的元素离散化后的值更小
    return val < o.val;
  }
} tmp[maxn];  // 也可以使用 std::pair

for (int i = 1; i <= n; ++i) tmp[i] = (Data){i, arr[i]};
sort(tmp + 1, tmp + n + 1);
for (int i = 1; i <= n; ++i) arr[tmp[i].idx] = i;

例题演示以下是调整后的文字格式:

题目描述

假定有一个无限长的数轴,数轴上每个坐标上的数初始值都是 0。

现在,我们首先进行 (n) 次操作,每次操作将某一位置 (x) 上的数加 (c)。

接下来,进行 (m) 次询问,每个询问包含两个整数 (l) 和 (r),你需要求出在区间 ([l,r]) 之间的所有数的和。

输入格式

  • 第一行包含两个整数 (n) 和 (m)。
  • 接下来 (n) 行,每行包含两个整数 (x) 和 (c)。
  • 再接下来 (m) 行,每行包含两个整数 (l) 和 (r)。

输出格式

  • 共 (m) 行,每行输出一个询问中所求的区间内数字和。

数据范围

输入样例

3 3
1 2
3 6
7 5
1 3
4 6
7 8

输出样例

8
0
5

解题思路:

对要操作的数据和区间[l,r]进行离散化,然后对需操作的数据进行+c操作,之后进行求前缀和s[i],最后对区间进行操作,获取所需操作区间离散化后的值,并求取前缀和即可。

图示中的alls对应代码中的arr

#include<bits/stdc++.h>
using namespace std;

typedef pair<int,int> pa;
//根据题意,操作数据最多有300000个
const int N=1e6+10;
int a[N],s[N];
vector<int> arr; //定义arr存储需要进行离散化的数据,并对其进行离散化操作
vector<pa> arrs,query;//arrs存储需要操作的数据(x, c),query存储区间[l, r]


int main(){
	int n,m;
	cin>>n>>m;
    //读取并存储操作(x, c)以及x
	for(int i=0;i<n;i++){
		int x,c;
		cin>>x>>c;
		arrs.push_back({x,c});//对操作(x, c)进行存储
		arr.push_back(x);//存储数轴坐标x
	}
    //读取操作区间[l, r],并将操作区间和数轴坐标l、r分别进行存储
	for(int i=0;i<m;i++){
		int l,r;
		cin>>l>>r;
		query.push_back({l,r});
		arr.push_back(l);
		arr.push_back(r);
	} 
    //对数据进行排序
	sort(arr.begin(),arr.end());
    //堆排序后数据进行去重操作
	arr.erase(unique(arr.begin(),arr.end()),arr.end());
    //获取离散化后数据对应的下标位置,并存入对应需操作的数据c
	for(auto i:arrs){
		auto x=lower_bound(arr.begin(),arr.end(),i.first)-arr.begin();
		a[x]+=i.second;
	}
    //前缀和操作
    //因为前面操作的数组a利用lower_bound()函数实现,所以下标从0开始
    //所以求取前缀和操作中对数组a,应该是加上a[i-1]从0开始进行加
    //但是前缀和数组s的下标依然是从1开始
	for(int i=1;i<=arr.size();i++)
		s[i]=s[i-1]+a[i-1];
    //获取操作区间l、r离散化后所对应的下标
	for(auto i:query){
		int l=lower_bound(arr.begin(),arr.end(),i.first)-arr.begin();
		int r=lower_bound(arr.begin(),arr.end(),i.second)-arr.begin();
		//arr的下标从0开始,而前缀和s下标从1开始
        //所以对于所需操作的r值应加1等价到前缀和s的下标
        //若不理解,建议自行作图
        cout<<s[r+1]-s[l]<<endl;
	}
	return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值