802.区间和 做题笔记

本题用到了离散化的思想。

离散化的本质是建立了一段数列到自然数之间的映射关系(value -> index),通过建立新索引,来缩小目标区间,使得可以进行一系列连续数组可以进行的操作比如二分,前缀和等…

离散化首先需要排序去重:

1. 排序:sort(alls.begin(),alls.end())
2. 去重:alls.earse(unique(alls.begin(),alls.end()),alls.end());

unique()函数的底层原理

vector<int>::iterator unique(vector<int> &a) {
    int j = 0;
    for (int i = 0; i < a.size(); ++i) {
        if (!i || a[i] != a[i - 1])//如果是第一个元素或者该元素不等于前一个元素,即不重复元素,我们就把它存到数组前j个元素中
            a[j++] = a[i];//每存在一个不同元素,j++
    }
    return a.begin() + j;//返回的是前j个不重复元素的下标
}

由于本题可能有多组数据是针对同一个数组下标操作的,因此我们可以将所有用到的数组下标装在一个下标容器alls内去重,然后再逐一为相同的数组下标增加数值c,再通过对应前缀和相减求得区间 l~r 之间的数的值。 

需要注意加深理解的点:

为什么要把lr加到alls容器里?答:l和r是原数轴上的下标,alls是所有需要用到的下标,而不是单单那些非0的点的下标,求区间和的时候有可能会用到那些非0点。二分查找的过程就是将这些有序原下标转化为连续的1,2,3,4,5.....下标的过程,这个下标是a数组的下标,a数组用来存储每次+c的数据。每次询问l和r之间的区间和,只需要计算[l,r]之间非零的数之和,我们最后计算用的前缀和s是a数组的前缀和,也就是需要经过原下标(l与r)—>离散后下标的转化,我们访问[l,r]区间也就是访问a数组的一个区间。 

其他重点见注释。

完整代码:

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

const int N=300010;
typedef pair<int,int> PII;
vector<int> alls;//存放下标
vector<PII>add,query;//存放操作的容器
int a[N],s[N];

int find(int x){//二分算法,把原下标映射成离散化下标
    int l=0,r=alls.size()-1;
    while(l<r){//查找大于等于x的最小的值的下标
        int mid=l+r>>1;
        if(alls[mid]>=x) r=mid;
        else l=mid+1;
    }
    return r+1;//因为使用前缀和,其下标要+1可以不考虑边界问题
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    while(n--){
        int x,c;
        scanf("%d%d",&x,&c);
        add.push_back({x,c});//放入操作容器
        alls.push_back(x);//放入原下标容器
    }
    while(m--){
        int l,r;
        scanf("%d%d",&l,&r);
        query.push_back({l,r});
        alls.push_back(l);alls.push_back(r);//注意lr边界也要放入alls
    }
    sort(alls.begin(),alls.end());//排序
    alls.erase(unique(alls.begin(),alls.end()),alls.end());//去重
    for(auto item:add){//处理插入
        int x=find(item.first);
        a[x]+=item.second;
    }
    for(int i=1;i<=alls.size();i++) s[i]=s[i-1]+a[i];//预处理前缀和
    for(auto item:query){//处理询问
        int l=find(item.first),r=find(item.second);//离散化
        printf("%d\n",s[r]-s[l-1]);//计算区间和
    }
}

 

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值