算法竞赛基础:离散化 ##C++实现(包含例题和源代码)

什么是离散化?

        离散化是一种常用的程序设计技巧,它可以有效地降低算法的时间复杂度。离散化的基本思想是在众多可能的情况中,只考虑需要用的值。具体来说,离散化就是把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。在不改变数据相对大小的条件下,对数据进行相应的缩小。

        例如,当我们只关心数据的大小关系时,可以用排名代替原数据进行处理。这在原数据很大或含有负数、小数时,难以表示为数组下标,一些算法和数据结构(如BIT)无法运作,这时我们就可以考虑将其离散化。

举个例子:

我们有这样的一些数字:

1 3 15 19 25 44 3000000 99999999

 虽然数字很少,但是如果我们想要去做一些标记处理的话,通常就需要开辟一个至少有99999999大小的数组了,但是在竞赛中,这样的算法通常过不了题目要求(不是超时就是超出内存),因此我们需要将他们离散化处理成为这样的数:

1 2 3 4 5 6 7 8 

我们只去关心他们之间的相对大小而不是绝对大小,这样处理之后,对于空间的利用率大大提升。

        离散化可以改进一个低效的算法,甚至实现根本不可能实现的算法。它在保持原序列大小关系的前提下把其映射成正整数。这样就可以极大地缩小空间复杂度和时间复杂度,且不改变原来的属性。即原来比你大,离散化后仍然比你大,只不过差距变小了而已。这就是离散化的基本概念和优势。

离散化怎么实现?

离散化的实现总体来说需要三个步骤:

1.排序

2.去重

3.偏移量转换

其中,最核心的步骤是第三步,这一步的作用就是将数值转换为离散化之后的值,而前两步都是为第三步做准备的。

接下来结合代码注释进行讲解:

#include <bits/stdc++.h>
using namespace std;
int a[200000+10];
int main() {
    int n ;
    cin >> n;
    
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]); //将要离散化的数据读入一个数组
    }
    sort(a + 1, a + n + 1); //进行排序
    
    int len = unique(a + 1, a + n + 1) - (a + 1);//去重
    
    for (int i = 1; i <= len; i++) { //输出离散化之前和离散化之后的数据
        cout << a[i] << ' ';
        a[i] = lower_bound(a + 1, a + len + 1, a[i]) - a;
        cout << a[i] << '\n';
    }
    return 0;
}

其中,lower_bound 函数返回的是一个迭代器,指向第一个大于等于 n 的元素的位置。

unique函数并非是将数组中的函数全部去重了,而是将其重复的函数放在了最后面,若我们不去访问他们,就实现了伪去重。

因此,若要实现真正的去重可以这这样做:

a.erase(unique(a.begin(), a.end()), a.end());

当然,lower_bound函数也可以自己去实现:

int find(int x)
{
	int l = 0, r = a.size() - 1;
	while (l < r)
	{
		int mid = l + r >> 1; //运用了位处理技巧,本质上就是除以2
		if(alls[mid] >= x)  r = mid; //二分查找
		else  l = mid + 1;
	}
	return r + 1;
}

 补充

在竞赛中,离散化往往是优化而不是解决问题的一种方式,例如,如何实现离散化之前和离散化之后的值的对应?这通常需要开辟多个数组来实现,我们以一道竞赛题来讲解:

 样例输入:
10 5 4
2 4
3 5
4 6
5 7
1 10
4 6 3 5

 样例输出:

4
3
3
4

 代码实现:

#include <bits/stdc++.h>
#define PII pair<int, int>
#define MAX_N 300005

using namespace std;

int a[MAX_N] = {0};
int s[MAX_N] = {0};
vector<PII> add;
vector<int> alls, query;

void inputadd(int m) {
    for (int i = 1, x, y; i <= m; i++) {
        scanf("%d %d", &x, &y);
        alls.push_back(x);
        alls.push_back(y);
        
        add.push_back({x, y});
    }
    return ;
}

void inputque(int q) {
    for (int i = 1, t; i <= q; i++) {
        scanf("%d", &t);
        query.push_back(t);
        alls.push_back(t);
    }
    return ;
}

void soruqe() {
    sort(alls.begin(), alls.end());
    unique(alls.begin(), alls.end());
    return ;
}

int nfind(int n) {
    return lower_bound(alls.begin(), alls.end(), n) - alls.begin();
}

void a_set() {
    for (auto it : add) {
        a[nfind(it.first)] += 1;
        a[nfind(it.second) + 1] -= 1;
    }
    return ;
}

void s_set() {
    s[0] = a[0];
    for (int i = 1; i <= alls.size(); i++) {
        s[i] = s[i - 1] + a[i];
    }
}

int main() {
    int n, m ,q;
    cin >> n >> m >> q;
    inputadd(m);
    inputque(q);
    soruqe();
    a_set();
    s_set();
    for (auto it : query) {
        printf("%d\n", s[nfind(it)]);
    }
    return 0;
}

本题利用前缀和和离散化的知识,没有学过前缀和处理方式的需要先去补课喽。

解释一下其中这些数组和容器的作用:
alls容器:记录所有值,包括要查询的草地值和释放E技能的值,排序和去重之后用作离散化求对应关系
query容器:记录要查询的值
add容器:记录E技能释放范围的值
a数组:存放的是E技能释放之后,离散值对应的范围(举个例子,例如25这个值,通过nfind(25)之后得到25离散化之后,在a中的下标,假设是2,那么a[2] += 1,表示从这开始往后是E技能释放的范围,直到遇到一个减一之后停下来)
s数组:表示a数组的前缀和。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

若亦_Royi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值