【算法】离散化

1.离散化的原理和模版

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

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

在这里插入图片描述

【离散化模板一】排序 + 去重 + 二分离散化之后的值

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

const int N = 1e5 + 10;

int n;
int a[N];

int pos;  //去重之后元素的个数
int disc[N]; //帮助离散化

//二分查找x所在的下标
int find(int x)
{
    int left = 1, right = pos;
    while(left < right)
    {
        int mid = (left + right) / 2;
        if(disc[mid] >= x) right = mid;
        else left = mid + 1;
    }
    return left;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        disc[++pos] = a[i];
    }
    
    sort(disc + 1, disc + 1 + pos); //排序
    pos = unique(disc + 1, disc + 1 + pos) - (disc + 1); //去重 + 计算去重之后元素的个数
    
    for(int i = 1; i <= n; i++)
    {
        cout << a[i] << "离散化之后:" << find(a[i]) << endl;
    }
    
    return 0;
}

【离散化模版二】排序 + 哈希表去重并且记录离散化之后的值

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

const int N = 1e5 + 10;

int n;
int a[N];

int pos;  //去重之后元素的个数
int disc[N]; //帮助离散化
unordered_map<int, int> hashMap; //<原始的值,离散化后的值>

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        disc[++pos] = a[i];
    }

    sort(disc + 1, disc + 1 + pos); //排序

    int cnt = 0; //当前这个值是第几号元素
    for (int i = 1; i <= pos; i++)
    {
        int x = disc[i];
        if (hashMap.count(x)) continue;
        ++cnt;
        hashMap[x] = cnt;
    }

    for (int i = 1; i <= n; i++)
    {
        cout << a[i] << "离散化之后:" << hashMap[a[i]] << endl;
    }

    return 0;
}

【注意事项】

  1. 离散化是一种「处理数据的技巧」,模版其实不用背,根据算法思想就可以实现。并且实现离散化的方式也可以在上述模板的基础上「修改」,使用的时候千万「不要生搬硬套」。
  2. 前期学习离散化的时候可能会被「绕」进去,会把「离散前」和「离散后」的值搞混,分不清楚是用离散前的值还是离散后的值。觉得迷惑是「很正常」,一定要根据离散化的原理「画图」分析整个流程。搞清楚每一个变量的作用以及达到的目的,就不会那么迷惑。

2.火烧赤壁

P1496 火烧赤壁

在这里插入图片描述

解法:离散化 + 差分

抛开数据范围不看,这就是一道「差分」题目:

  1. 给定一个区间,我们可以全部执行 +1 操作。
  2. 最后看看整个数组中,大于 0 的位置有多少个。

因此可以创建一个原数组的「差分」数组,然后执行完「区间修改」操作之后,还原原数组,「统计大于 0」的区间长度。

但是,这道题的「数据范围」不允许我们直接差分,因为「开不了那么大」的数组。即使能开那么大的数组,「时间」也不够用。我们发现,区间的范围虽然很大,区间的「个数」却只有 2 × 10^4 级别。此时我们就可以:

  1. 先将所有的「区间信息」离散化。
  2. 然后在「离散化的基础」上,处理所有的「区间修改」操作。
  3. 处理完之后找出「原始数组对应的区间端点」,计算相应的「长度」。
#include<iostream>
#include<algorithm>
#include<unordered_map>
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> id; //<原始的值,离散化后的值>

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++)
    {
        id[disc[i]] = i; //原始的值->离散化后的值
    }
    
    //在离散化之后的基础上做差分
    for(int i = 1; i <= n; i++)
    {
        int l = id[a[i]], r = id[b[i]];
        f[l]++; f[r]--;
    }
    
    //还原数组
    for(int i = 1; i <= pos; i++)
    {
        f[i] += f[i - 1];
    }
    
    //统计结果
    int ret = 0;
    for(int i = 1; i <= pos; i++)
    {
        int j = i;
        while(j <= pos && f[j] > 0) j++;
        ret += (disc[j] - disc[i]); //累加上原始区间的长度
        i = j;
    }
    cout << ret << endl;
    
    return 0;
}

3.贴海报

P3740 [HAOI2014] 贴海报

在这里插入图片描述

解法:离散化

根据题意「模拟」即可。

由于「区间的长度」很大,暴力模拟的时候会超时。但是我们发现,虽然区间长度很大,但是「区间的个数」是很少的,所以我们可以「离散化」处理一下区间的端点值,然后在「离散化的基础上」模拟覆盖情况。

注意注意注意,重要的注意说三遍:

离散化在离散「区间问题」的时候一定要小心!因为我们离散化操作会把区间缩短,从而导致丢失一些点。在涉及「区间覆盖」问题上,离散化会导致「结果出错」。比如如果有三个区间分别为:[2, 5],[2, 3],[5, 6],离散化之后为:[1, 3],[1, 2],[3, 4],区间覆盖如图所示:

在这里插入图片描述

为了避免出现上述情况,我们可以在离散化的区间 [x, y] 时,不仅考虑 x, y 这两个值,也把「x + 1, y + 1」也考虑进去。此时「单个区间内部」就出现空隙,「区间与区间之间」也会出现空隙。就可以避免上述情况出现。可见,离散化之后可能会导致结果错误,使用的时候还是需要「谨慎」一点。

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

const int N = 1e3 + 10;

int n, m;
int a[N], b[N];

int pos;
int disc[N * 4]; //要离散化的数组
unordered_map<int, int> id; //<原始的值,离散化后的值>

int wall[N * 4];
bool st[N * 4]; //标记哪些数字(海报)已经出现过

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        cin >> a[i] >> b[i];
        disc[++pos] = a[i]; disc[++pos] = a[i] + 1;
        disc[++pos] = b[i]; disc[++pos] = b[i] + 1;
    }
    
    //离散化
    sort(disc + 1, disc + 1 + pos);
    int cnt = 0;
    for(int i = 1; i <= pos; i++)
    {
        int x = disc[i];
        if(id.count(x)) continue;
        cnt++;
        id[x] = cnt;
    }
    
    //在离散化的基础上,模拟贴海报的过程
    for(int i = 1; i <= m; i++)
    {
        //离散化之前的值:a[i]~b[i]
        int left = id[a[i]], right = id[b[i]];
        for(int j = left; j <= right; j++)
        {
            wall[j] = i;
        }
    }
    
    //统计结果:wall数组中有多少不同的数(海报)
    int ret = 0;
    for(int i = 1; i <= cnt; i++)
    {
        int x = wall[i];
        if(x == 0 || st[x] == true) continue;
        ret++;
        st[x] = true;
    }
    cout << ret << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值