AcWing 802. 区间和

题目

算法思路:离散化

为什么要离散化?存储的下标实在太大了,如果直接开这么大的数组,根本不现实,第二个原因,本文是数轴,要是采用下标的话,可能存在负值。

离散化的本质:将间隔很大的点,映射到相邻的数组元素中。减少对空间的需求,也减少计算量。

(图源于acwng--liangshang大佬~~~)

第一步:定义变量

  • N:数组as的容量,最大的数据量为300010。(总共离散化的数的规模是n+2*m)
  • nm:分别表示插入操作的次数和查询操作的次数。
  • a:存储每个离散化后的坐标点对应的
  • s:存储a的前缀和,以便快速计算区间和
  • allsvector<int>,用于存储所有与插入和查询相关的坐标点,用于离散化。
  • addquery:两个vector<pair<int, int>>,分别用于存储插入操作(坐标x和值c)和查询操作(查询区间的左右端点l,r)。
    int n,m;
    const int N=300010;
    typedef pair<int,int> PII;
    int a[N],s[N];
    vector<int> alls;
    vector<PII> add,query;
    

第二步:输入数据(插入和查询操作,存储在pair数组)

cin>>n>>m;
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;
    alls.push_back(l);
    alls.push_back(r);
    query.push_back({l,r});
}
  • 读取n个添加操作,每个操作包含xc,并将x加入alls用于离散化。
  • 读取m个查询操作,每个查询操作包含左右边界lr,并将lr分别加入alls用于离散化。

对所有坐标进行离散化(排序+去重--------背过!!!)

sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());

第三步:处理添加操作(获得a数组)

for(auto item:add)
{
    int x=find(item.first);
    a[x]+=item.second;
}

对于每个添加操作,首先通过find函数找到坐标x离散化后的索引位置,然后将对应位置的值增加item.second

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在离散化后的alls数组中的位置,返回的是离散化后的索引。

第四步:计算前缀和

for(int i=1;i<=alls.size();i++) s[i]=s[i-1]+a[i];

由步骤三获得的a数组,通过累加前面的值计算前缀和数组ss[i]表示从第一个位置到第i个位置的和。

第五步:处理查询操作,输出结果

for(auto item:query)
{
    int l=find(item.first),r=find(item.second);
    cout<<s[r]-s[l-1]<<endl;
}

对于每个查询操作,先找到lr离散化后的索引,然后利用前缀和数组 s 计算区间[l, r]的和,并输出结果。

示例

四次插入,三次查询

4 3  
3 5  
1 7  
6 1  
10 2  
2 4  
3 7  
8 10
  1. 离散化前alls = [3, 1, 6, 10, 2, 4, 3, 7, 8, 10](包括所有插入和查询的坐标,注意有重复的坐标)。(ps:一开始我非常不理解为啥要把两次不同的操作存在一个数组中,后面彻底理解了之后,石角实女少啊)

  2. 离散化后(排序去重):alls = [1, 2, 3, 4, 6, 7, 8, 10]

  3. 插入操作-四次-获取a数组(注意数组as的下标从1开始,与alls的下标有偏移,所以二分返回的是r+1):

  • a[find(3)](即a[3])处插入5。

  • a[find(1)](即a[1])处插入7。
  • a[find(6)](即a[5])处插入1。
  • a[find(10)](即a[8])处插入2。

   a数组变为[0, 7, 0, 5, 0, 1, 0, 0, 0, 2]

     4.计算前缀和

s[1] = a[1] = 7  
s[2] = s[1] + a[2] = 7 + 0 = 7  
s[3] = s[2] + a[3] = 7 + 5 = 12  
s[4] = s[3] + a[4] = 12 + 0 = 12  
s[5] = s[4] + a[5] = 12 + 1 = 13  
s[6] = s[5] + a[6] = 13 + 0 = 13  
s[7] = s[6] + a[7] = 13 + 0 = 13  
s[8] = s[7] + a[8] = 13 + 0 = 13  
s[9] = s[8] + a[9] = 13 + 0 = 13  
s[10] = s[9] + a[10] = 13 + 2 = 15

    5.查询操作---输出区间和

  • 查询区间[2, 4],即s[find(4)] - s[find(2)-1] = s[4] - s[1] = 12 - 7 = 5
  • 查询区间[3, 7],即s[find(7)] - s[find(3)-1] = s[7] - s[2]

总代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=300010;
typedef pair<int,int> PII;
int a[N],s[N];
vector<int> alls;
vector<PII> add,query;
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; 
}
int main()
{
    cin>>n>>m;
    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;
        alls.push_back(l);
        alls.push_back(r);
        query.push_back({l,r});
    }
    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);
        cout<<s[r]-s[l-1]<<endl;
    }
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值