UVa 10587 Mayor‘s Posters
题目描述
Bytetown\texttt{Bytetown}Bytetown 的市长选举中,候选人们随意在墙上张贴海报,引起了市民的不满。市议会决定建造一面专门的选举墙,并制定以下规则:
- 每位候选人只能在墙上贴一张海报
- 所有海报高度与墙相同,宽度可以是任意整数字节(byte\texttt{byte}byte 是 Bytetown\texttt{Bytetown}Bytetown 的长度单位)
- 墙被分成若干段,每段宽 111 字节
- 每张海报必须完全覆盖一段连续的墙段
墙的总长度为 100000001000000010000000 字节。当竞选活动重新开始时,候选人们按顺序在墙上张贴海报,这些海报的宽度差异很大,而且后来的海报会覆盖之前海报的部分区域。
你的任务是找出在所有海报都张贴完毕后,有多少张海报仍然可见(即使只看到一部分也算可见)。
输入格式
第一行包含一个整数 ccc,表示测试用例的数量。
每个测试用例的第一行包含一个整数 nnn (1≤n≤100001 \leq n \leq 100001≤n≤10000),表示海报的数量。
接下来的 nnn 行按张贴顺序描述海报,每行包含两个整数 lil_ili 和 rir_iri,表示第 iii 张海报覆盖的墙段范围(从 lil_ili 到 rir_iri,包含端点)。
输出格式
对于每个测试用例,输出在所有海报都张贴完毕后,仍然可见的海报数量。
题目分析
问题核心
这是一个典型的区间覆盖问题,需要处理以下关键点:
- 覆盖顺序:后张贴的海报会覆盖之前海报的对应部分
- 大规模数据:墙的长度达到 10710^7107,但海报数量最多只有 100001000010000
- 可见性判断:海报只要有一部分未被覆盖就算可见
解题思路
逆向处理
采用从后往前的处理顺序:
- 最后一张海报肯定完全可见
- 倒数第二张海报只有未被之后海报覆盖的部分才可见
- 依此类推…
这样我们只需要维护一个数据结构来记录当前哪些墙段已经被后续海报覆盖。
离散化优化
由于墙的长度很大 (10710^7107) 但实际用到的端点很少(最多 200002000020000 个),可以使用离散化技术:
- 收集所有海报的端点坐标
- 排序并去重
- 建立原始坐标到紧凑索引的映射
这样就将坐标范围从 [1,107][1, 10^7][1,107] 压缩到 [1,20000][1, 20000][1,20000] 左右。
线段树维护
使用线段树配合懒标记来高效处理区间查询和更新:
- 查询:判断当前海报区间内是否还有未被覆盖的段
- 更新:将当前海报覆盖的区间标记为已覆盖
算法步骤
- 读取所有海报的区间信息
- 收集所有端点坐标,排序去重进行离散化
- 建立原始坐标到离散化索引的映射
- 从最后一张海报到第一张海报依次处理:
- 将当前海报区间映射到离散化坐标
- 查询该区间是否还有未被覆盖的部分
- 如果有,则这张海报可见,计数器加 111
- 将该区间标记为已覆盖
- 输出可见海报数量
复杂度分析
- 离散化:O(nlogn)O(n \log n)O(nlogn)
- 线段树操作:每次查询和更新都是 O(logm)O(\log m)O(logm),其中 mmm 是离散化后的坐标数量
- 总复杂度:O(nlogn+nlogm)O(n \log n + n \log m)O(nlogn+nlogm),完全适合 n≤10000n \leq 10000n≤10000 的数据规模
代码实现
// Mayor's Posters
// UVa ID: 10587
// Verdict: Accepted
// Submission Date: 2025-10-21
// UVa Run Time: 0.020s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <bits/stdc++.h>using namespace std;const int MAXN = 10000; // 最大海报数量struct Poster {int l, r; // 海报覆盖的左右端点
} posters[MAXN + 5];vector<int> vals; // 存储所有端点坐标用于离散化
unordered_map<int, int> mapping; // 原始坐标到离散化索引的映射// 线段树相关
const int MAXM = 20000; // 离散化后的最大坐标数量
bool tree[4 * MAXM]; // 线段树,记录区间是否完全被覆盖
bool lazy[4 * MAXM]; // 懒标记,表示该区间需要被覆盖// 下推懒标记
void push_down(int idx) {if (lazy[idx]) {tree[idx * 2] = true;tree[idx * 2 + 1] = true;lazy[idx * 2] = true;lazy[idx * 2 + 1] = true;lazy[idx] = false;}
}// 查询区间 [ql, qr] 是否全部被覆盖
bool query(int idx, int l, int r, int ql, int qr) {if (ql > r || qr < l) return true; // 无交集,视为已覆盖if (tree[idx]) return true; // 整个区间已被覆盖if (ql <= l && r <= qr) {return tree[idx]; // 当前区间完全在查询范围内}push_down(idx); // 下推懒标记int mid = (l + r) / 2;bool left_covered = true, right_covered = true;if (ql <= mid)left_covered = query(idx * 2, l, mid, ql, qr);if (qr > mid)right_covered = query(idx * 2 + 1, mid + 1, r, ql, qr);return left_covered && right_covered; // 左右子树都覆盖才算完全覆盖
}// 更新区间 [ql, qr] 为已覆盖
void update(int idx, int l, int r, int ql, int qr) {if (ql > r || qr < l) return; // 无交集,直接返回if (ql <= l && r <= qr) {tree[idx] = true; // 当前区间完全在更新范围内lazy[idx] = true; // 设置懒标记return;}push_down(idx); // 下推懒标记int mid = (l + r) / 2;if (ql <= mid)update(idx * 2, l, mid, ql, qr);if (qr > mid)update(idx * 2 + 1, mid + 1, r, ql, qr);tree[idx] = tree[idx * 2] && tree[idx * 2 + 1]; // 更新父节点
}int main() {ios::sync_with_stdio(false);cin.tie(0);int c;cin >> c; // 测试用例数量while (c--) {int n;cin >> n; // 海报数量vals.clear();mapping.clear();// 读取所有海报区间for (int i = 0; i < n; i++) {cin >> posters[i].l >> posters[i].r;vals.push_back(posters[i].l);vals.push_back(posters[i].r);}// 离散化处理sort(vals.begin(), vals.end());vals.erase(unique(vals.begin(), vals.end()), vals.end());int m = vals.size(); // 离散化后的坐标数量// 建立映射关系for (int i = 0; i < m; i++) {mapping[vals[i]] = i + 1; // 索引从1开始}// 初始化线段树memset(tree, 0, sizeof(tree));memset(lazy, 0, sizeof(lazy));int visible = 0; // 可见海报计数器// 从后往前处理海报for (int i = n - 1; i >= 0; i--) {int l = mapping[posters[i].l]; // 映射左端点int r = mapping[posters[i].r]; // 映射右端点// 查询当前海报区间是否还有未被覆盖的部分if (!query(1, 1, m, l, r)) {visible++; // 有未被覆盖的部分,该海报可见}// 更新线段树,标记该区间为已覆盖update(1, 1, m, l, r);}cout << visible << "\n";}return 0;
}
总结
本题通过逆向处理、离散化和线段树的巧妙结合,高效地解决了大规模区间覆盖的可见性问题。逆向处理保证了我们只需要关心后续海报的覆盖情况,离散化将问题规模从 10710^7107 压缩到 2×1042 \times 10^42×104 级别,线段树则提供了高效的区间查询和更新操作。这种组合思路在处理大规模区间问题时非常实用。