离散化与区间合并
离散化
离散化的概念
离散化本质上可以看成是一种 哈希,其保证数据在哈希以后仍然保持原来的全/偏序关系。
通俗地讲就是当有些数据因为本身很大或者类型不支持,自身无法作为数组的下标来方便地处理,而影响最终结果的只有元素之间的相对大小关系时,我们可以将原来的数据按照从大到小编号来处理问题,即离散化。
例题:区间和
题目描述
假定有一个无限长的数轴,数轴上每个坐标上的数都是
现在,我们首先进行
接下来,进行
输入格式
第一行包含两个整数
接下来
再接下来
输出格式
共
输入样例
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例
8
0
5
题目分析
对于该题,很容易想到可以用前缀和来处理连续区间的和,但是若将输入的
所以我们就需要考虑,能否不直接把
试想,数字的数量最多是
像本题的区间和问题,那就可以对 存
可以发现,存
理论可行,关键就在于如何把所有的
让我们一步一步来,首先先抛开要加的
不过要注意的是,题目输入的
另外,为了之后查找
vector<int> alls;
for (int i=1; i<=n; i++) {
cin >> x >> c;
alls.push_back(x);
}
for (int i=1; i<=m; i++) {
cin >> l >> r;
alls.push_back(l);
alls.push_back(r);
}
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
接下来处理第二个问题,怎么把
要知道一点,到这里 alls
数组已经进行离散化了,是一个有序且不重复的数组,每个数字
所以,解决方案就是:通过二分查找找到这个
int a[300005];
vector<pair<int,int>> add;
int find(int x) { // 二分查找第一个大于等于x的位置
int l = 0, r = alls.size()-1, mid;
while (l < r) {
mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid+1;
}
return r+1; // 加1是为了让离散化的下标从1开始,方便求前缀和。
}
for (int i=1; i<=n; i++) {
cin >> x >> c;
add.push_back({x, c});
// 这里省略上面那段处理 alls 数组的代码。
}
for (auto item : add) {
int x = find(item.first); // 查找 x 离散化后的下标
a[x] += item.second; // 对应下标上加上 c
}
最后的查找也很简单,先对 a
数组求出前缀和后,只需要在输入时把
示例代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
int n, m;
int a[N], s[N];
vector<int> alls;
vector<PII> add, query;
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;
}
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;
query.push_back({l, r});
alls.push_back(l);
alls.push_back(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;
}
区间合并
基本概念
所谓区间合并,就是对给定的n个区间,将有交集(包含端点)的若干个小区间合并为一个大区间。
例题:区间合并
题目描述
给定
输入格式
第一行包含整数
接下来
数据范围:
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
输入样例
5
1 2
2 4
5 6
7 8
7 9
输出样例
3
题目分析
从两个区间的位置关系出发进行考虑,假设已经按区间的左端点进行排序,有如下三种位置关系:
令当前区间的左端点为 st
,右端点为 ed
,假设待合并区间为 i.st
、i.ed
。
合并思路:贪心!
情况①:当前区间包含待合并区间时,st, ed => st, ed
,还是原区间。
情况②:当前区间与待合并区间相交时,st, ed => st, i.ed
,拓展了右区间。
情况③:当前区间与待合并区间不相交时,st, ed => i.st, i.ed
,原区间已经是合并完成的独立区间,从下一个新区间重新合并下一个区间。
示例代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
void merge(vector<PII> &segs) // 合并两个区间
{
vector<PII> res;
sort(segs.begin(), segs.end()); // pair默认按左端点排序
int st = -2e9, ed = -2e9; // 初始化起始断点-∞
for (auto seg : segs)
if (ed < seg.first) // 情况3不相交
{
// 初始-∞端点要跳过
if (st != -2e9) res.push_back({st, ed});
st = seg.first, ed = seg.second;
}
else ed = max(ed, seg.second); // 情况1,2相交,取右端点较大值
// n=0时特判、最后一个区间压入
if (st != -2e9) res.push_back({st, ed});
segs = res;
}
int main() {
int n;
scanf("%d", &n);
vector<PII> segs;
for (int i = 0; i < n; i ++ ) {
int l, r;
scanf("%d%d", &l, &r);
segs.push_back({l, r});
}
merge(segs); // 传引用参数
cout << segs.size() << endl;
return 0;
}