【算法训练(day7)】区间和并,离散化数组模板

目录

一.区间和并

二 .离散化数组


一.区间和并

 问题:给定 n个区间 [li,ri],要求合并所有有交集的区间。注意如果在端点处相交,也算有交集。输出合并完成后的区间个数。例如:[1,3][1,3] 和 [2,6][2,6] 可以合并为一个区间 [1,6][1,6]。

  1. 实现功能及思路:将所有含有交集的区间和并。需要用到有序的性质。我们把每段的起始位置进行排序。当我们合并两端不同的区间时有可能会出现三种情况。第一段区间和第二段区间没有交集,或者是第一段区间包含整个第二段的区间。又或者是第一段区间包含了部分第二段区间。我们只需要分别处理这三种情况并记录下来就可以完成区间和并。第一种情况的时候没有交集所以两端区间无法合并,第二种情况因为第一段包含了第二段所以第一段区间就是合并后的区间。第三种情况,从第一段的起始位置,到第二段的结束位置就是合并后的新区间的位置。
  2. 代码实现:
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    void merge(vector>& opr)
    {
        vector> ret;
        sort(opr.begin(), opr.end());   //需要借助有序的性质
    
        int st = -1e9 - 10, ed = -1e9 - 10;
        for (auto pt : opr)
        {
            if (ed < pt.first)             //处理无法合并的部分 st————————ed
            {                               //                                 st——————ed
                if (ed != -1e9 - 10)        //当前物件直接加入结果,继续遍历后序数组。
                {
                    ret.push_back({ st,ed });
                }
                st = pt.first;
                ed = pt.second;
            }
            else
            {
                ed = max(ed, pt.second);   //这里处理的是两种情况      1.st——————————ed    2.st——————————ed
                                            //                            st————ed              st————————ed
            }
        }
        if (ed != -1e9 - 10)
        {
            ret.push_back({ st,ed });
        }
        cout << ret.size();
        return;
    }
    int main()
    {
        int n = 0;
        cin >> n;
        vector> opr;
        for (int i = 0; i < n; i++)
        {
            int l, r;
            cin >> l >> r;
            opr.push_back({ l,r });
        }
        merge(opr);
        return 0;
    }

二 .离散化数组

问题:假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。现在,我们首先进行 n 次操作,每次操作将某一位置 x上的数加 c。接下来,进行 m次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r]之间的所有数的和。【算法训练(day7)】区间和并,离散化数组模板_第1张图片

 

  1. 实现功能及思路:先前我们处理过前缀和问题。当时要求前缀和的数据是连续的。且数量不多。如果要求的前缀和数据不是连续的且分散的很远(有效数据之间有若干0)的话再用之前的算法效率就会很低。因为如果继续按照之前的算法我们是要遍历所有数据所有范围的。这时候我们就可以用一个离散化数组。也就是哈希映射,把原先分散的值的下标重新映射到一个新的连续的数组上。这时候就可以继续正常求前缀和了。

    数据范围

  2. 细节处理:  虽然我们只有1e5个数据但是因为在进行前缀和的预处理的时候,我们的范围包括查询的次数,所以实际应该是n+2m。 同时构建哈希映射数组的时候不单单只能包含插入数据的映射关系,还要包含查询数据的映射关系,因为我们查询的时候也需要去离散数组里去确定范围。    对插入和查询的下标的排序是在map里进行的 
  3. 代码实现:
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    //离散化数组+前缀和求解(因为题目的数据范围很广但数据量很稀疏,若是直接用前缀和求解效率过于低下,所以先用哈希把数组的有效元素位置哈希映射后形成一个较小的离散化数组后在使用前缀和)
    const int N = 3e5 + 10;  //范围需要提前考虑前缀和的预处理
    int a[N];//存放构造出来的离散化数组,用来后需求前缀和
    int b[N];//存放根据离散化数组构建出来的前缀和
    vector> add, que;//所有增加操作和查询操作数组。 
    map repst;//存放构造的哈希数组和实际值之间的对应关系
    int main()
    {
        //构建离散化数组和真实数组的关系
        int n = 0,m = 0;
        cin >> n >> m;
        for (int i = 0; i < n; i++)  //保存所有的插入操作(插入位置和插入值)
        {
            int x,c;   //x是真实位置,c是插入值
            cin >> x >> c;
            add.push_back({ x,c }); 
            repst[x] = 0;   //i就是新的下表
        }
    
        for (int j = 0; j < m; j++)  //保存所有的查询操作(查询的位置也要构建对应关系,因为我们要到离散化数组里去找值,不能用原值去找)
        {
            int l, r;
            cin >> l >> r;
            que.push_back({ l,r });
            repst[l] = 0;
            repst[r] = 0;
        }
        int pos = 1;//映射新下标
        for (auto &pt : repst)
        {
            pt.second = pos++;
        }
        //插入修改操作
        for (auto input : add)
        {
            int pt = repst[input.first];//离散化后的新下标
            a[pt] += input.second;
        }
        //前缀和预处理
        for (int i = 1; i <= repst.size(); i++)  //repst.size()表示map repst中不同元素的个数,也就是离散化后数组的大小。因此,for循环的循环变量i的取值范围为[1, repst.size()],对于每个i,都计算出a[1]到a[i]的前缀和计算前缀和的范围不能只是插入值的范围,不然就会少计算那些没离散化的0.
        {
            b[i] = a[i] + b[i - 1];
        }
        //处理询问
        for (auto output : que)
        {
            int l = repst[output.first];//离散化后的新下标
            int r = repst[output.second];//离散化后的新下标
    
            cout << b[r] - b[l - 1] << endl;
        }
        return 0;
    }

你可能感兴趣的:(算法,算法,c++)