题目
算法思路:离散化
为什么要离散化?存储的下标实在太大了,如果直接开这么大的数组,根本不现实,第二个原因,本文是数轴,要是采用下标的话,可能存在负值。
离散化的本质:将间隔很大的点,映射到相邻的数组元素中。减少对空间的需求,也减少计算量。
(图源于acwng--liangshang大佬~~~)
第一步:定义变量
N
:数组a
和s
的容量,最大的数据量为300010。(总共离散化的数的规模是n+2*m)n
和m
:分别表示插入操作的次数和查询操作的次数。a
:存储每个离散化后的坐标点对应的值。s
:存储a
的前缀和,以便快速计算区间和。alls
:vector<int>
,用于存储所有与插入和查询相关的坐标点,用于离散化。add
和query
:两个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
个添加操作,每个操作包含x
和c
,并将x
加入alls
用于离散化。 - 读取
m
个查询操作,每个查询操作包含左右边界l
和r
,并将l
和r
分别加入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数组,通过累加前面的值计算前缀和数组s
,s[i]
表示从第一个位置到第i
个位置的和。
第五步:处理查询操作,输出结果
for(auto item:query)
{
int l=find(item.first),r=find(item.second);
cout<<s[r]-s[l-1]<<endl;
}
对于每个查询操作,先找到l
和r
离散化后的索引,然后利用前缀和数组 s 计算区间[l, r]
的和,并输出结果。
示例
四次插入,三次查询
4 3
3 5
1 7
6 1
10 2
2 4
3 7
8 10
-
离散化前:
alls = [3, 1, 6, 10, 2, 4, 3, 7, 8, 10]
(包括所有插入和查询的坐标,注意有重复的坐标)。(ps:一开始我非常不理解为啥要把两次不同的操作存在一个数组中,后面彻底理解了之后,石角实女少啊) -
离散化后(排序去重):
alls = [1, 2, 3, 4, 6, 7, 8, 10]
。 -
插入操作-四次-获取a数组(注意数组
a
和s
的下标从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;
}