区间和 离散化入门与例题· C++

简介: 区间和 离散化入门与例题· C++

关于离散化,OIWIKI上是这么解释的:


       通俗地讲就是当有些数据因为本身很大或者类型不支持,自身无法作为数组的下标来方便地处理,而影响最终结果的只有元素之间的相对大小关系时,我们可以将原来的数据按照从大到小编号来处理问题,即离散化。


用再通俗的话来讲,就是用下标替代原数值,解决一些特定问题;


什么样的特定问题呢?


影响结果的只有原数值的相对大小且原数值分布的比较稀疏(或者说差距比较大)


即 相对大小+差距大


比如给出3个坐标,1,100000,1000000000,其中每个坐标上分别对应一个权值a,b,c,其余没提到的坐标上对应的权值均是0;


那么如果我要求坐标区间内[l,r]的和,那么很显然,这是一个很明显的前缀和问题。


容易想到,我们创建一个数组,预处理后作一个前缀和数组,就可以解决问题了;


可是问题在于,当坐标过大的时候,数组没办法开那么大,即开头提到的:


自身无法作为数组的下标。


那么,于是,就有了离散化。


离散化本质上是一种hash


原因在于,我们可以把数值映射成坐标


即用坐标替代数值,数值的相对大小表现为坐标的相对大小;


离散化操作通常需要先排序,然后去重,然后得到正确的映射。


所以,讲到这里,可以把离散化理解为一种映射(只不过需通过一些操作才能获得正确映射)。


以Acwing 802区间和为例,以经典例题的角度展开叙述:


802. 区间和


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


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


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


输入格式


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


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


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


输出格式


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


数据范围


−109≤x≤109

1≤n,m≤105

−109≤l≤r≤109

−10000≤c≤10000


问题分析:


很显然,下标的大小达到了10的9次方,显然无法直接开等大的数组,但是我们可以离散化!


还是前面那句话!离散化就是把数值映射成下标。


       那么可以看到,有n行输入,输入了n个坐标,和与其坐标对应的权值(应该累加的值);


那么考虑最坏的情况,如果这n个坐标互异,那么我们也仅仅需要开1e5大小的数组;


可以发现空间大幅度减小!


       接下来的m行输入,每一行输入一对(l,r),也就是一对坐标x1,x2。由于我们最终要求[x1,x2]内的元素和,所以我们关心x1和x2上的权值,因而有必要在离散化的时候把x1,x2也进行一个映射。同样的,若这2m个坐标互异,2m<=2*1e5, 那么我们再开2*1e5大小的空间,也足以应付!(使得每个坐标都是一个唯一确定的下标与之对应)


因而,我们开一个坐标数组alls,对于每一个坐标,存入alls[i];


等价于alls[i]这个坐标映射成了下标i!


那么该如何实现这个操作?


很简单,我们先读入所有的坐标,push_back到alls数里面,然后sort一遍


就可以实现 若i<j,必定alls[i]<alls[j]的逻辑关系,即开头提到的用坐标替代数值,数值的相对大小表现为坐标的相对大小;


然后我们需要去重,这一步可能比较突然,后面会解释。(其实当前可以这么理解,既然是映射,我们设坐标为x,x通过映射法则映射成f(x),那么根据数学知识,我们直到一个x仅且对应一个f(x),而一个f(x)是可以对应多个x)


所以目前,我们就形成了一个映射体系,即坐标和下标的一 一对应关系,用数组alls体现.


当坐标离散化后,由于我们想求的是元素和,那么需要关注权值;


权值仅出现在输入的时候,而我们希望把权值放到对应的坐标上,


换句话说,我们更希望,把权值放到对应的坐标所对应的离散化下标上。


但是,我们无法做到一边读入一边离散化,而我们上述的希望是基于离散化工作完成的基础上,那么,我们需要一个容器,暂时存下输入,vector<pair<int,int>> add,即操作向量


同样的,我们再准备一个操作向量,vector<pair<int,int>>query,暂时地把“询问”存起来。


当读入完毕后,我们就需要把权值放上去,因而离不开一个原数组(这里有暗示了,和前缀和相对应),我们创建它。


遍历add,每个元素的第一个位置存的是坐标,第二个元素存的是当前应该累加的值c;


那么该如何把坐标转化成对应的下标?由于alls这个坐标数组是排好序的且是一 一对应(这时候你就知道为什么要去重了),我们可以二分查找,找到坐标对应的下标i,执行a[i]++c;


那么,遍历完后,a[i]代表某个坐标上的权值(某个坐标:下标i相对应的坐标);


所以我们到这里就实现了,把权值放到对应的坐标所对应的离散化下标上。


那么接下来,原数组创建好了,下一步就是求终极任务:元素和


在原数组的基础上,我们开一个前缀和数组s(下标从1开始,避免特判)


那么s[i]=a[1]+a[2]+..a[i](1<2<...i,所对应的下标x1<x2<..xi)


s[i]可以表述为数轴上x1+x2+..xi的和


因而给定一组(i,j),我们列:s[i]-s[j-1] = a[j]+a[j+1]...a[i](每个元素都非0)(xj<xj+1<...xi)


表述为数轴上[xj,xi]的元素和,(非0的元素和+0的元素和)


这时候利用我们之前开的query数组,读入l,r;


由于l,r是真实的坐标,我们需要将其映射到下标,还是用到二分!


把[l,r]转化为[j,i],所以求xj+...xi = aj+...ai = s[i] - s[j-1],大功告成!


#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 3e5+10;
int a[N],s[N];
typedef pair<int,int>PII;
vector<PII> add,query;
vector<int>alls;
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 r+1;//找到第一个大于等于x的下标
}
int main()
{
    int n,m;
    cin >> n >> m;
    int x,c;
    for (int i = 1;i <= n;i++)
    {
        cin >> x >> c;
        alls.push_back(x);
        add.push_back({x,c});
    }
    int l,r;
    for (int i = 0;i<m;i++)
    {
        cin >> l >> r;
        query.push_back({l,r});
        alls.push_back(l);
        alls.push_back(r);
    }
    sort(alls.begin(),alls.end());
    alls.erase(unique(alls.begin(),alls.end()),alls.end());
   for (auto i:add)
   {    
       int j = find(i.first);//使得下标x先映射到下标,再在下标数组中运算
       a[j]+=i.second;
   }
   for (int i = 1;i<=alls.size();i++) s[i] =s[i-1]+a[i];
   for (auto i:query)
   {
       int p = find(i.first),q = find(i.second);
       cout<<s[q] - s[p-1]<<endl;
   }
}

c6e1b1626f0d4691ab788049c9e36ae7.png

我是小郑,正在奔赴热爱,奔赴山海,体会数学和算法之美~

相关文章
|
2月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
47 2
C++入门12——详解多态1
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
88 1
|
2月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
28 0
|
2月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
37 0
|
2月前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
2月前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
2月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
39 0
|
2月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
41 0
|
2月前
|
存储 算法 C++
C++入门10——stack与queue的使用
C++入门10——stack与queue的使用
46 0
|
2月前
|
存储 C++ 容器
C++入门9——list的使用
C++入门9——list的使用
21 0