算法小白逆袭记!离散化与区间合并的魔法之旅

68 阅读4分钟

算法小白逆袭记!离散化与区间合并的魔法之旅 ✨

你是否也曾被海量数据搞得晕头转向?🤯 今天我们来学习两个"整理小能手"——离散化和区间合并,让复杂问题瞬间变简单!

🧩 离散化:给无限数轴贴标签

什么是离散化?举个栗子🌰

想象你有一个无限长的衣柜(就像数学里的数轴),但你只在少数几个位置挂了衣服(有值的坐标)。如果要统计某段区间有多少件衣服,总不能把整个衣柜都翻一遍吧?

离散化就是:只把挂了衣服的位置记下来,给它们编上号,其他位置直接忽略!就像给图书馆里需要的书架贴标签🏷️,不用管那些空书架~

核心工具:快递单和去重器 📦🧹

在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个独立的便利贴~

核心思想:排好队,手拉手👫

  1. 先按起点给所有区间排好队
  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 区间合并

离散化:"我擅长处理‘无限大但稀疏’的数据,把沙漠变成小花园!"
区间合并:"我擅长整理‘重叠混乱’的区间,把乱麻梳成直线!"
小白:"有你们在,算法题再也不可怕啦!💪"

希望这篇生动的算法讲解能帮你轻松掌握离散化和区间合并!记得点赞收藏哦~ 👇