最长的白色段

这道题目是一道经典的区间染色问题。虽然数轴范围高达 \(10^9\),但由于染色操作次数 \(N\) 较小(最大 5000),我们可以通过坐标离散化动态维护区间的方法来解决。


解题思路

  1. 离散化 (Discretization)

    数轴范围巨大,但 \(N\) 次操作涉及的端点最多只有 \(2N = 10,000\) 个。我们将所有涉及到的 \(a_i\)\(b_i\) 收集起来,从小到大排序并去重,形成一系列相互连接的小区间。

  2. 区间状态维护

    离散化后,原本连续的数轴被切成了若干个“原子区间”。我们可以用一个数组来记录每个区间的颜色。

  3. 模拟染色

    遍历每一次输入,将对应范围内的“原子区间”标记为白色或黑色。

  4. 合并与统计

    染色结束后,遍历所有区间,将连续的白色区间合并,记录长度最大且起始点最小的那一段。


C++ 实现代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>

using namespace std;

struct Query {
    int l, r;
    char color;
};

int main() {
    int N;
    if (!(cin >> N)) return 0;

    vector<Query> ops(N);
    vector<int> coords;
    // 初始区间端点,题目范围是 0 到 10^9
    coords.push_back(0);
    coords.push_back(1000000000);

    for (int i = 0; i < N; ++i) {
        cin >> ops[i].l >> ops[i].r >> ops[i].color;
        coords.push_back(ops[i].l);
        coords.push_back(ops[i].r);
    }

    // 1. 离散化:排序并去重
    sort(coords.begin(), coords.end());
    coords.erase(unique(coords.begin(), coords.end()), coords.end());

    // 2. 初始化颜色:0表示黑色,1表示白色。初始全为白色。
    // 离散化后 m 个点产生 m-1 个区间
    int m = coords.size();
    vector<int> color(m, 1);

    // 3. 模拟染色过程
    for (int i = 0; i < N; ++i) {
        // 找到当前操作在离散化数组中的下标
        int L = lower_bound(coords.begin(), coords.end(), ops[i].l) - coords.begin();
        int R = lower_bound(coords.begin(), coords.end(), ops[i].r) - coords.begin();

        int c = (ops[i].color == 'w' ? 1 : 0);
        for (int j = L; j < R; ++j) {
            color[j] = c;
        }
    }

    // 4. 统计最长白色段
    int max_len = -1;
    int ans_l = 0, ans_r = 0;

    int cur_l = -1;
    for (int i = 0; i < m; ++i) {
        if (i < m - 1 && color[i] == 1) {
            if (cur_l == -1) cur_l = coords[i]; // 记录段起点
        } else {
            if (cur_l != -1) {
                int cur_r = coords[i]; // 记录段终点
                int len = cur_r - cur_l;
                if (len > max_len) {
                    max_len = len;
                    ans_l = cur_l;
                    ans_r = cur_r;
                }
                cur_l = -1;
            }
        }
    }

    if (max_len == -1) return 0;
    cout << ans_l << " " << ans_r << endl;

    return 0;
}

关键点拨

  • 离散化原理

    假设有两个操作 \([10, 20]\)\([15, 30]\)。端点集合为 \(\{10, 15, 20, 30\}\)。这会产生三个区间:\([10, 15]\)\([15, 20]\)\([20, 30]\)。通过操作下标,我们可以精准地控制这些小区间的状态。

  • 复杂度分析

    • 排序与去重:\(O(N \log N)\)
    • 染色操作:\(O(N^2)\)(最坏情况下每次都要遍历所有原子区间)
    • 鉴于 \(N=5000\)\(N^2\) 约为 \(2.5 \times 10^7\),在 1 秒的限时内完全可以跑通。
  • 边界处理

    注意题目中数轴范围到 \(10^9\),但在初始时全为白色,因此默认把 \(0\)\(10^9\) 加入坐标集合。

详细注释版代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 定义一个结构体来存储每一次染色的操作信息
struct Query {
    int l, r;
    char color;
};

int main() {
    // 快速输入输出优化
    ios::sync_with_stdio(false);
    cin.tie(NULL);

    int N;
    if (!(cin >> N)) return 0;

    vector<Query> ops(N);
    vector<int> coords;

    // 初始状态:数轴 0 到 10^9 全是白色
    // 我们必须把数轴的起始边界放进坐标集
    coords.push_back(0);
    coords.push_back(1000000000);

    for (int i = 0; i < N; ++i) {
        cin >> ops[i].l >> ops[i].r >> ops[i].color;
        // 收集所有出现过的端点,用于后续“打断”数轴
        coords.push_back(ops[i].l);
        coords.push_back(ops[i].r);
    }

    // --- 第一步:离散化处理 ---
    // 为什么要离散化?因为坐标到10^9,开不了那么大的数组。
    // 但 5000 个操作最多涉及 10000 个端点,这些端点把数轴分成了有限个区间。
    sort(coords.begin(), coords.end());
    // 去除重复坐标,确保 coords 中每个元素都是唯一的升序点
    coords.erase(unique(coords.begin(), coords.end()), coords.end());

    // --- 第二步:初始化颜色数组 ---
    // 假设 coords 有 m 个点,那么它们之间就有 m-1 个原子区间
    // 例如 coords 是 {0, 47, 634, 10^9},区间就是 [0,47], [47,634], [634,10^9]
    int m = coords.size();
    // color[j] 表示区间 [coords[j], coords[j+1]] 的颜色
    // 1 表示白色 (white), 0 表示黑色 (black)
    // 根据题意,初始全是白色
    vector<int> color(m - 1, 1);

    // --- 第三步:模拟染色 ---
    for (int i = 0; i < N; ++i) {
        // 使用二分查找定位当前操作的左右端点在离散化数组中的下标
        int L = lower_bound(coords.begin(), coords.end(), ops[i].l) - coords.begin();
        int R = lower_bound(coords.begin(), coords.end(), ops[i].r) - coords.begin();

        int c = (ops[i].color == 'w' ? 1 : 0);

        // 将 [L, R) 范围内的所有原子区间都染成目标颜色
        // 注意:下标 R 对应的坐标是端点,区间只需要染到 R-1
        for (int j = L; j < R; ++j) {
            color[j] = c;
        }
    }

    // --- 第四步:扫描结果,寻找最长连续白段 ---
    int max_len = -1;
    int ans_l = 0, ans_r = 0;

    int cur_start = -1; // 记录当前正在统计的白色段起点坐标

    for (int i = 0; i < m - 1; ++i) {
        if (color[i] == 1) { // 如果当前区间是白色
            if (cur_start == -1) {
                cur_start = coords[i]; // 发现新白段,记录起点
            }
            // 如果是最后一段或者下一段不是白色,则当前白段在这里结束
            if (i == m - 2 || color[i + 1] == 0) {
                int cur_end = coords[i + 1];
                int current_total_len = cur_end - cur_start;

                // 更新最大长度:由于我们要找最小的解,这里只在严格大于时更新
                // 这样如果长度相同,我们会保留先发现的(起点更小的)那一段
                if (current_total_len > max_len) {
                    max_len = current_total_len;
                    ans_l = cur_start;
                    ans_r = cur_end;
                }
                cur_start = -1; // 重置起点,准备找下一段
            }
        }
    }

    // 输出最终结果
    cout << ans_l << " " << ans_r << endl;

    return 0;
}

核心逻辑图解

为了更直观,你可以想象数轴被端点切成了若干磁带盒:

  1. 坐标点 (coords)[0, 43, 47, 300, 634, 10^9]
  2. 原子区间
    • 槽位 0: [0, 43]
    • 槽位 1: [43, 47]
    • 槽位 2: [47, 300]
    • 槽位 3: [300, 634]
    • ...以此类推
  3. 染色操作:当你要把 4347 染黑时,代码会通过二分查找发现这对应“槽位 1”,然后把 color[1] 设为 0。
  4. 统计:最后只需要看哪些槽位是连在一起的 1,把它们的原始物理长度(coords[i+1] - coords[i])加起来即可。

这份代码的时间复杂度是 \(O(N^2)\),空间复杂度是 \(O(N)\),对于题目给出的 \(N=5000\) 的规模,运行效率非常理想。

posted @ 2026-01-29 18:03  张一信奥  阅读(7)  评论(0)    收藏  举报