离散化

这里将介绍离散化算法

底层原理

有些数据因为本身很大或者类型不支持,自身无法作为数组的下标来方便地处理,而影响最终结果的只有元素之间的相对大小关系时,我们可以建立这段数据自然数的映射关系,(value -> index),通过建立新索引,来缩小目标区间,使得可以进行一系列连续数组可以进行的操作比如二分,前缀和等…

离散化问题解题模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // 映射到1, 2, ...n
}

注: 代码来源于y总!

题目练习

第一题 (来自acwing)

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

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

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

输入格式

第一行包含两个整数 n 和 m。

接下来 n 行,每行包含两个整数 x 和 c。

再接下来 m 行,每行包含两个整数 l 和 r。

输出格式

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

数据范围

$-10^9≤x≤10^9$,

$1≤n,m≤10^5$,

$-10^9≤l≤r≤10^9$,

$-10000≤c≤10000$

输入样例

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

输出样例

1
2
3
8
0
5

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

const int N=300010;//坐标x的数量上限为1e5,两个坐标l,r的数量上限也为1e5,所以加起来为3*le5;
typedef pair<int,int> PII;//pair<int,int>类似于结构体的简写版,typedef定义了一个新的类型PII(跟结构体定义了一个结构体类型然后使用相似)

int a[N],s[N];

vector<int> alls;
vector<PII> add,query;
//使用二分查找x所在的位置,此时是alls(x,l,r)排好序的,返回的坐标也会是按照x的大小所给出的;
int find(int x)
{
    int l=0,r=alls.size()-1;
    while(l<r)
    {
        int mid=(l+r)/2;
        if(alls[mid]>=x) r=mid;
        else l=mid+1;
    }
    return r+1;//因为后续要使用前缀和,所以返回的坐标要加上1;
}

int main()
{
    int n,m;cin>>n>>m;
    //分别将要操作的四组数据记录在add和query中,将l,r,x的坐标值保存在alls中;
    for(int i=0;i<n;i++)
    {
        int x,c;
        cin>>x>>c;
        add.push_back({x,c});
        alls.push_back(x);
    }
    for(int i=0;i<m;i++)
    {
        int l,r;
        cin>>l>>r;
        query.push_back({l,r});
        alls.push_back(l);
        alls.push_back(r);
    }
    //将alls进行排序,并将重复的操作删除掉(如进行了两次在x的增值操作,应该去掉一个x保持平衡);
    sort(alls.begin(),alls.end());
    alls.erase(unique(alls.begin(),alls.end()),alls.end());
    //一个迭代器从1开始直到末尾结束,itdm.first是x,second是c(在上方循环中可知);
    
    for(auto itdm : add)
    {
        int x;
        x=find(itdm.first);
        a[x]+=itdm.second;
    }
//只循环x是因为x的坐标加上了值,而l,r可能有(l或r与x的值相同),且定义全局变量数组,其余值默认为0,故可以方便计算;
    
    for(int i=1;i<=alls.size();i++) s[i]=s[i-1]+a[i];

    for(auto itdm : query)
    {
        int l,r;
        l=find(itdm.first);//找出l,r在a中的坐标
        r=find(itdm.second);
        printf("%d\n",s[r]-s[l-1]);//前缀和上方已计算,所以可直接输出,记得加上换行符!
    }
    return 0;
}

第二题(来自洛谷B3694)

数列离散化

题目描述

给定一个长度为 $n$ 的数列 $a$。定义 $\mathrm{rank}(i)$ 表示数列 $a$ 中比 $a_i$ 小的不同数字个数再加一。

对 $1 \leq i \leq n$,现在请你求出所有的 $\mathrm{rank}(i)$。

输入格式

本题单测试点内有多组测试数据

输入的第一行是一个整数,表示数据组数 $T$。接下来依次给出每组数据的信息:

第一行是一个整数,表示数列长度 $n$。
第二行有 $n$ 个整数表示数列 $a$,第 $i$ 个整数表示 $a_i$。

输出格式

对每组数据,输出一行 $n$ 个整数,用空格隔开,依次表示 $\mathrm{rank}(1)$ 到 $\mathrm{rank}(n)$。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
3
3
1 2 3
5
1 6 2 2 7
4
-1 -2 -3 -3

样例输出 #1

1
2
3
1 2 3
1 3 2 2 4
3 2 1 1

提示

数据规模与约定

对全部的测试点,保证 $1 \leq T \leq 5$,$1 \leq n \leq 10^5$,$-10^9 \leq a_i \leq 10^9$。

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

vector<int> alls;  // 存储所有待离散化的值

// 自定义二分查找函数,找到 x 在离散化数组中的位置
int find(int x) {
    int l = 0, r = alls.size() - 1;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (alls[mid] >= x)
            r = mid;
        else
            l = mid + 1;
    }
    return l + 1;  // 将索引映射到 1, 2, ... n
}

int main() {
    int T;
    cin >> T;

    while (T--) {
        int n;
        cin >> n;

        vector<int> a(n);
        alls.clear();

        // 读取输入,同时加入到 alls 中以便离散化
        for (int i = 0; i < n; i++) {
            cin >> a[i];
            alls.push_back(a[i]);
        }

        // 对 alls 进行排序和去重,完成离散化
        sort(alls.begin(), alls.end());
        alls.erase(unique(alls.begin(), alls.end()), alls.end());

        // 计算每个元素的排名
        for (int i = 0; i < n; i++) {
            int rank = find(a[i]);  // 使用自定义的二分查找获取排名
            cout << rank << " ";
        }
        cout << endl;
    }

    return 0;
}

总结

注意掌握离散化的核心原理和解题模板,才能更好地解决问题!最后谢谢大家的支持!

最后更新于 Nov 16, 2024 16:23 UTC