算法小白逆袭记!离散化与区间合并的魔法之旅 ✨
你是否也曾被海量数据搞得晕头转向?🤯 今天我们来学习两个"整理小能手"——离散化和区间合并,让复杂问题瞬间变简单!
🧩 离散化:给无限数轴贴标签
什么是离散化?举个栗子🌰
想象你有一个无限长的衣柜(就像数学里的数轴),但你只在少数几个位置挂了衣服(有值的坐标)。如果要统计某段区间有多少件衣服,总不能把整个衣柜都翻一遍吧?
离散化就是:只把挂了衣服的位置记下来,给它们编上号,其他位置直接忽略!就像给图书馆里需要的书架贴标签🏷️,不用管那些空书架~
核心工具:快递单和去重器 📦🧹
在C++里我们需要两个"神器":
- pair:像快递单一样,能同时存地址(x)和物品(c)
- unique:去重小能手,把重复的标签撕掉(比如同一个位置贴了两张标签)
实战例题:沙漠插旗问题 🏜️🚩
问题:在无限长的数轴上,我们做n次"插旗"(给x位置加c),然后m次询问某区间[l,r]有多少旗子。
数据范围:x能从-10^9到10^9(比沙漠还大!),但n和m只有1e5(旗子其实不多)
解题魔法步骤:
1️⃣ 收集所有关键位置📍:把所有插旗位置和询问区间的端点都记下来
vector<int> alls; // 装所有需要关注的坐标
// 收集插旗位置
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); // 记录区间端点
}
2️⃣ 排序去重🧹:给收集的位置排好队,撕掉重复标签
sort(alls.begin(), alls.end()); // 排好队
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去重
3️⃣ 召唤二分查找小助手🔍:把真实坐标变成我们的"标签号"
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开始,方便算前缀和
}
4️⃣ 前缀和计算🧮:用标签号计算区间和
// 给标签位置赋值
for (auto item : add) {
int x = find(item.first); // 真实位置→标签号
arr[x] += item.second; // 在标签位置放旗子
}
// 算前缀和
for (int i = 1; i <= alls.size(); i++) {
s[i] = s[i - 1] + arr[i];
}
// 回答询问
for (auto item : query) {
int l = find(item.first), r = find(item.second);
cout << s[r] - s[l - 1] << endl; // 前缀和相减得区间和
}
✨ 离散化模板(拿走不谢!)
// 1. 收集所有待离散化的值
vector<int> alls;
// 2. 排序并去重
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
// 3. 二分查找映射函数
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; // 映射到1-based
}
🔄 区间合并:把重叠的便利贴粘起来
什么是区间合并?看这个例子📝
如果有一堆便利贴:[1,3]、[2,6]、[8,10],其中[1,3]和[2,6]有重叠,就可以粘成一个大便利贴[1,6]!最后剩下2个独立的便利贴~
核心思想:排好队,手拉手👫
- 先按起点给所有区间排好队
- 依次检查:如果当前区间能和前一个"手拉手"(有重叠),就合并成一个大区间;否则就作为新的开始
实战例题:合并重叠区间 🧩
问题:n个区间,合并所有有交集的区间(端点相交也算),输出最终有几个区间。
输入:5个区间 [1,2] [2,4] [5,6] [7,8] [7,9]
输出:3(合并后是 [1,4] [5,6] [7,9])
解题步骤:
1️⃣ 给区间排好队📏:按左端点从小到大排序
sort(a.begin(), a.end()); // pair排序默认先比first,再比second
2️⃣ 合并区间🔗:用两个变量记录当前合并区间的起点和终点
vector<pii> merge(vector<pii>& A) {
sort(A.begin(), A.end());
int st = -1e9, ed = -1e9; // 初始区间(不存在)
vector<pii> res;
for (auto it : A) {
if (it.first > ed) { // 当前区间和上一个不重叠
if (st != -1e9) res.push_back({st, ed}); // 把上一个区间加入结果
st = it.first, ed = it.second; // 开始新的合并区间
} else { // 有重叠,扩展终点
ed = max(ed, it.second);
}
}
if (st != -1e9) res.push_back({st, ed}); // 加入最后一个区间
return res;
}
✨ 区间合并模板(拿走不谢!)
void merge(vector<pii> &segs) {
vector<pii> res;
sort(segs.begin(), segs.end()); // 按左端点排序
int st = -2e9, ed = -2e9; // 初始区间(比最小可能值还小)
for (auto seg : segs) {
if (ed < seg.first) { // 无重叠,保存上一个区间
if (st != -2e9) res.push_back({st, ed});
st = seg.first, ed = seg.second;
} else { // 有重叠,更新终点
ed = max(ed, seg.second);
}
}
if (st != -2e9) res.push_back({st, ed}); // 处理最后一个区间
segs = res; // 结果覆盖原数组
}
🎉 算法小剧场:离散化 vs 区间合并
离散化:"我擅长处理‘无限大但稀疏’的数据,把沙漠变成小花园!"
区间合并:"我擅长整理‘重叠混乱’的区间,把乱麻梳成直线!"
小白:"有你们在,算法题再也不可怕啦!💪"
希望这篇生动的算法讲解能帮你轻松掌握离散化和区间合并!记得点赞收藏哦~ 👇