区间合并
AcWing 803.区间合并
给定 n 个区间 [ li,ri ],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3]和[2,6]可以合并为一个区间[1,6]。
输入格式
第一行包含整数n。
接下来n行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
1≤n≤100000
−109≤li≤ri≤109
输入样例:
5
1 2
2 4
5 6
7 8
7 9
输出样例:
3
思路:
先按照区间左边界进行依次排序,挨个处理,然后判断右边界的关系:
假设当前区间[ l1,r1 ],下一个区间[ l2,r2 ],首先我们可以确定l1≤ l2 一定成立,那么这两个区间可能存在三种关系:
- r2≤r1,暗含l2≤r1,即包含关系,此时可以忽略该区间
- r2>r1&&l2≤r1,此时只要修改当前区间为[ l1,r2 ]即可
- r2>r1&&l2>r1,说明这两个区间毫无关系,当前区间已经可以算为合并后的一个新区间了,开始判断下一个合并区间。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
vector<PII> merge(vector<PII> &segs)
{
vector<PII> res;//初始下res为空
if (segs.size()!=0){
int l=segs[0].first,r=segs[0].second;
for (int i = 1; i < segs.size(); ++i) {
if (segs[i].first > r){
res.push_back({
l,r});
l=segs[i].first; r=segs[i].second;
}
else r=max(r,segs[i].second);
}
res.push_back({
l,r}); //把最后剩下的放进去。
}
return res;
}
int main() {
int n,l,r;
cin>>n;
vector<PII> segs;
for (int i = 0; i < n; ++i) {
scanf("%d%d",&l,&r);
segs.push_back({
l,r});
}
sort(segs.begin(),segs.end());
auto res=merge(segs);
cout<<res.size()<<endl;
// for (int i = 0; i < res.size(); ++i) {
// cout<<res[i].first<<" "<<res[i].second<<endl;
// }
return 0;
}
离散化
其实,这里说的离散化更像是对于稀疏矩阵的一种新的存储方式,类似于映射吧。

AcWing 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
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5
思路:
这种题需要考虑的数组长度(1e9)特别大,也就是说需要开很大的数组才能做到求其前缀和,但是考虑到用到的数并没有那么多,只有1e5,中间有很多地方值都是0,可忽略。即如果开那么大的数组老老实实的相加求前缀和是很麻烦的,因为数组很稀疏,所以我们考虑将涉及到的位置(下标)根据其从小到大的顺序映射到一个新的数组中,
接下来要考虑到修改原数组中某一位置/下标的时,需要找到该位置对应新数组的下标,我们可以将输入数据时的数存入新数组,但是当查询时,一些位置下标可能没有存入我们的新数组中,这样就找不到,这样,一些用于查询的下标就也需要存入我们的新数组中,所以考虑到实际情况,我们用到的下标最多有n+2*m个,即3e5,要注意去重!(考虑到重复:输入数据(x,c)进行修改时的重复以及查询时下标(l,r)已在数组中存储过的重复,所以实际上可能没有3e5这么多)
当我们要修改数轴上 下标 为k的值:将其加c,通过查找到x在数组a中的下标得到值x,即a[x]=k
num数组用于记录用到的数轴上用到的下标处的对应的值
修改后num数组对应的位置不为0,那些用于查找但不修改的位置对应的仍为0,由于num数组还要用 于求前缀和,所以num数组从1开始存储,而a数组从0开始存储,所以查到的k实际还要再加1才能对 应num数组。(因为不涉及到重复元素,所以+1即可)
即,修改完(加c后),num[k+1]+=c; 即num[a[x]+1]+=c;
还有一个问题,原坐标轴上的位置/下标存入新数组a后,怎么在新数组a中找到原位置呢?
我们通过二分法不断逼近找到原数值,并返回其在新数组中的下标。
这样的 思路 和桶排,是完全相反的思路。
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
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; // 映射到1, 2, ...n
}
unique(alls.begin(), alls.end()) 是c++的一个函数,负责筛选出所有的重复的元素,并将其放在容器的后面,返回不重复时的最大下标:alls.begin()+k。
在得到下标后:alls.erase(alls.begin()+k, alls.end()); 即可去除重复元素。
二者合起来就是:alls.erase(unique(alls.begin(), alls.end()), alls.end());
unique函数也可自己写:
数组中不重复的两个条件:(在数组有序的前提下)
- 是数组的第一个数
- a[i]!=a[i-1]
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])
a[j ++ ] = a[i];
// a[0] ~ a[j - 1] 所有a中不重复的
return a.begin() + j;
}
最终代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=3e5+10;
int num[N]; //模拟数轴上的数值,默认全0
int s[N];//求num数组的前缀和
vector<int> a; //存储数值:用到的下标,和num数组对应,只不过num数组从1存储,a数组从0存储
vector<PII> add,b;//add数组用于n项+操作,b数组用于查询求和。
int find(int x)
{
int l=0,r=a.size()-1;
while (l<r){
int mid=l+r>>1;
if (a[mid]>=x) r=mid;
else l=mid+1;
}
return l+1; //对应num数组从1存储,方便求前缀和
}
int main() {
int n,m,x,c,l,r;
cin>>n>>m;
for (int i = 0; i < n; ++i) {
scanf("%d%d",&x,&c);
a.push_back(x);
add.push_back({
x,c});
}
for (int i = 0; i < m; ++i) {
scanf("%d%d",&l,&r);
a.push_back(l); a.push_back(r);
b.push_back({
l,r});
}
sort(a.begin(),a.end());
a.erase(unique(a.begin(),a.end()),a.end());
for (auto item : add){
x=find(item.first);
num[x]+=item.second;
}
for (int i = 1; i <= a.size(); ++i) s[i]+=s[i-1]+num[i];
for (auto item : b){
l=find(item.first),r=find(item.second);
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}