1 条题解
-
0
题目理解
这是一个 图的覆盖问题。
- 地形为 ( N \times M ) 的网格,每个格子有海拔高度。
- 蓄水厂只能建在第 1 行的城市(与湖泊相邻)。
- 输水站可以建在某个城市,当且仅当:
- 存在一个相邻城市(四连通,即上下左右)海拔比它高
- 并且那个相邻城市已经有了水利设施(蓄水厂或输水站)
- 目标:第 N 行(干旱区)所有城市都要有水利设施。
如果无法满足,输出第 N 行不能覆盖的城市数; 如果能满足,输出所需的最少蓄水厂数量。
思路分析
关键转化:
- 建输水站的条件其实等价于从高处向低处流动,因此我们可以从每个蓄水厂开始,模拟水流,看它能覆盖到哪些城市。
- 水流只能从海拔高的地方流向相邻的海拔低的地方(注意这里只能从高到低,不能平着流)。
- 每个蓄水厂能覆盖到干旱区(第 N 行)的某些城市。这些覆盖范围是连续的区间吗?
不是,因为地形起伏,可能覆盖第 N 行的几个不连续的城市。
子问题 1:判断能否全部覆盖
如果我们从第一行的每个城市出发,模拟水流(DFS 或 BFS),可以求出它能覆盖第 N 行的哪些城市。
如果所有第 N 行城市都能被至少一个第一行的出发城市覆盖,则能满足要求,否则不能。
子问题 2:最少蓄水厂数量
如果满足覆盖,问题变成:
已知第一行的 M 个起点,每个起点能覆盖干旱区的一些城市(即覆盖第 N 行的若干列),选择最少的起点,使得它们的覆盖范围并集是整个第 N 行。这就是一个区间覆盖问题的变种:
- 第一行的每个城市 i 对应一个能覆盖干旱区的一个列集合(不一定是连续区间)。
- 但我们可以这样处理:对于第 N 行的每一列 j,找出所有能覆盖它的起点(第一行的列号),然后问题转化为:选择最少的起点列,使得第 N 行的每一列都被至少一个选中的起点覆盖。
这其实是一个 集合覆盖问题,在 M ≤ 500 时较难直接贪心得到最优解,需要搜索或DP。
但是,本题有一个重要性质:
对于一个起点,它覆盖的第 N 行的列集合是连续的区间。证明思路(可以验证):水流从高到低,地形网格上,从第一行的某个点流到第 N 行,受限于四连通且只能从高到低,因此它在干旱区覆盖的列号是连续的。
题目样例给出的图也符合这一点。因此,问题转化为:
第一行的每个城市 i(1 ≤ i ≤ M)能覆盖干旱区的一个区间 [L[i], R[i]](L[i] 和 R[i] 是列号,1-based)。
我们要选最少的区间,使得它们完全覆盖 1 到 M。
区间完全覆盖的贪心算法:
- 对区间按左端点 L 排序。
- 设当前覆盖到位置 now = 0(列从 1 开始,所以初始未覆盖)。
- 在所有 L ≤ now+1 的区间里,选择 R 最大的,然后 now = R,答案+1,直到 now ≥ M。
- 如果某一步找不到 L ≤ now+1 的区间,则覆盖失败(但根据前提,全覆盖是可能的)。
算法步骤
- 从第一行的每个城市做 DFS/BFS 标记可达区域,并记录它能到达的第 N 行的最小列号和最大列号,作为区间 [L[i], R[i]]。
- 注意:如果某个第一行城市不能到达任何干旱区城市,则它没有区间(不选它)。
- 判断干旱区每个城市是否被至少一个区间覆盖,如果不是,则输出 0 和干旱区不能覆盖的数量。
- 如果全覆盖,则用上述区间覆盖贪心求最少起点数。
时间复杂度
BFS/DFS 从每个第一行城市出发:O(M * N * M) 如果每次重新搜索是 O(NM²) 可能超时(N,M ≤ 500 时太大)。
优化:
- 记忆化:对于每个格子,记录能从第一行的哪些列流过来(bitset 或 bool 数组)。
但更简单的是反向思考:
从干旱区的每个城市出发,逆流(从低到高,相邻且高度更高)能到达第一行的哪些城市。
这样只需要对每个干旱区城市做一次 BFS,就能知道它被哪些第一行城市覆盖。
复杂度 O(NM) 对每个干旱区城市 BFS 最坏 O(NM) → 总 O(NM²) 还是大?
实际上,每个干旱区城市 BFS 范围有限(因为逆流只能到更高),但最坏可能 O(NM) 每个,总共 O(NM²) 在 500²=250k 个干旱区城市?不对,干旱区只有 M 个城市,每个 BFS 最多访问所有 N×M 个格子,所以总 O(NM²) 在 500*500²=125M 可能稍大,但 BFS 有 visited 剪枝,并且每个格子只被每个起点的 BFS 访问一次,还是可能超时。
更优方法:
从第一行的每个点做一次 BFS,记录覆盖的干旱区列范围,总共 M 次 BFS,每次 O(NM) → O(NM²),在 N=500, M=500 时是 1.25 亿操作,勉强可过(常数小的话)。
但我们可以用 DP 思想:
设 reachable[i][j] 表示能否从第一行的某个城市流到 (i,j)。
我们可以从所有第一行城市同时开始 BFS(多源 BFS),一次得到所有可达点,但这样无法区分每个起点覆盖的干旱区列区间。因此,为了得到每个起点的覆盖区间,只能单独 BFS。
M ≤ 500,N ≤ 500,MNM=125M,在合理优化下(队列、标记数组复用)可以过。
详细步骤
- 输入 N, M 和高度矩阵 H。
- 对于第一行的每个列 j=1..M:
- BFS/DFS 从 (1,j) 出发,标记所有能到达的点。
- 记录能到达的干旱区(第 N 行)的最小列号 L[j] 和最大列号 R[j]。 如果没到达任何干旱区城市,则 L[j] = INF, R[j] = -1。
- 检查干旱区每个城市(第 N 行,列 k=1..M):
- 是否被某个 j 覆盖(即存在 j 使得 L[j] ≤ k ≤ R[j])。
- 统计不能被覆盖的个数 cnt。
- 如果 cnt > 0:
- 输出
0和 cnt。
- 输出
- 否则:
- 收集所有 L[j] ≤ R[j] 的区间 [L[j], R[j]]。
- 用贪心区间覆盖算法求最少区间数(即最少蓄水厂数)。
- 输出
1和最少数量。
贪心区间覆盖实现
区间按 L 排序,设当前覆盖到 now=0,每次从 L ≤ now+1 的区间里选 R 最大的,更新 now = R,计数+1,直到 now ≥ M。
代码框架(C++)
#include <iostream> #include <vector> #include <queue> #include <algorithm> #include <cstring> using namespace std; const int MAXN = 505; const int INF = 1e9; int N, M; int H[MAXN][MAXN]; bool vis[MAXN][MAXN]; int L[MAXN], R[MAXN]; // 第一行第j列能覆盖干旱区的区间 bool covered[MAXN]; // 干旱区第k列是否被覆盖 int dx[4] = {1, -1, 0, 0}; int dy[4] = {0, 0, 1, -1}; void bfs(int sx, int sy, int idx) { queue<pair<int, int>> q; q.push({sx, sy}); vis[sx][sy] = true; L[idx] = INF; R[idx] = -1; while (!q.empty()) { int x = q.front().first, y = q.front().second; q.pop(); if (x == N) { // 干旱区 L[idx] = min(L[idx], y); R[idx] = max(R[idx], y); } for (int d = 0; d < 4; d++) { int nx = x + dx[d], ny = y + dy[d]; if (nx >= 1 && nx <= N && ny >= 1 && ny <= M && !vis[nx][ny] && H[nx][ny] < H[x][y]) { vis[nx][ny] = true; q.push({nx, ny}); } } } } int main() { cin >> N >> M; for (int i = 1; i <= N; i++) for (int j = 1; j <= M; j++) cin >> H[i][j]; // 对第一行每个城市 BFS for (int j = 1; j <= M; j++) { memset(vis, 0, sizeof(vis)); bfs(1, j, j); } // 检查干旱区覆盖情况 int cnt_uncovered = 0; for (int k = 1; k <= M; k++) { covered[k] = false; for (int j = 1; j <= M; j++) { if (L[j] <= k && k <= R[j]) { covered[k] = true; break; } } if (!covered[k]) cnt_uncovered++; } if (cnt_uncovered > 0) { cout << 0 << endl << cnt_uncovered << endl; return 0; } // 区间覆盖贪心 vector<pair<int, int>> intervals; for (int j = 1; j <= M; j++) { if (L[j] <= R[j]) { intervals.push_back({L[j], R[j]}); } } sort(intervals.begin(), intervals.end()); int now = 0, ans = 0, i = 0; int m = intervals.size(); while (now < M) { int max_r = now; while (i < m && intervals[i].first <= now + 1) { max_r = max(max_r, intervals[i].second); i++; } if (max_r == now) break; // 无法扩展 now = max_r; ans++; } cout << 1 << endl << ans << endl; return 0; }
- 1
信息
- ID
- 5426
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者