最长的白色段
这道题目是一道经典的区间染色问题。虽然数轴范围高达 \(10^9\),但由于染色操作次数 \(N\) 较小(最大 5000),我们可以通过坐标离散化或动态维护区间的方法来解决。
解题思路
-
离散化 (Discretization):
数轴范围巨大,但 \(N\) 次操作涉及的端点最多只有 \(2N = 10,000\) 个。我们将所有涉及到的 \(a_i\) 和 \(b_i\) 收集起来,从小到大排序并去重,形成一系列相互连接的小区间。
-
区间状态维护:
离散化后,原本连续的数轴被切成了若干个“原子区间”。我们可以用一个数组来记录每个区间的颜色。
-
模拟染色:
遍历每一次输入,将对应范围内的“原子区间”标记为白色或黑色。
-
合并与统计:
染色结束后,遍历所有区间,将连续的白色区间合并,记录长度最大且起始点最小的那一段。
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;
}
核心逻辑图解
为了更直观,你可以想象数轴被端点切成了若干磁带盒:
- 坐标点 (coords):
[0, 43, 47, 300, 634, 10^9] - 原子区间:
- 槽位 0:
[0, 43] - 槽位 1:
[43, 47] - 槽位 2:
[47, 300] - 槽位 3:
[300, 634] - ...以此类推
- 槽位 0:
- 染色操作:当你要把
43到47染黑时,代码会通过二分查找发现这对应“槽位 1”,然后把color[1]设为 0。 - 统计:最后只需要看哪些槽位是连在一起的
1,把它们的原始物理长度(coords[i+1] - coords[i])加起来即可。
这份代码的时间复杂度是 \(O(N^2)\),空间复杂度是 \(O(N)\),对于题目给出的 \(N=5000\) 的规模,运行效率非常理想。

浙公网安备 33010602011771号