UVa 10599 Robots(II)
题目描述
你的公司提供机器人用于在体育赛事和音乐会结束后清理场地上的垃圾。在分配机器人执行任务之前,场地的航拍照片会被标记为一个网格。网格中包含垃圾的位置都会被标记出来。
所有机器人从西北角(左上角)出发,在东南角(右下角)结束移动。机器人只能朝两个方向移动:向东(右)或向南(下)。当机器人进入包含垃圾的单元格时,可以被编程拾取该垃圾后再继续前进。一旦机器人到达东南角的目的地,就不能重新定位或重复使用。
由于你的费用与特定任务中使用的机器人数量直接相关,你希望充分利用每个机器人。你的任务是使用一个机器人清理尽可能多的含垃圾单元格。由于可能存在多种方式实现这一目标,你需要报告能够清理的最大单元格数量、达到该数量的方式总数,并展示其中一种具体的清理序列。
输入格式
输入文件包含一个或多个场地地图,以包含 -1 -1 的行表示输入结束。每个场地地图的描述以下列方式组织:
- 第一行:两个整数,表示网格的行数 RRR 和列数 CCC
- 随后若干行:每行两个整数,表示包含垃圾的行号和列号
- 以
0 0表示当前场地地图的结束
输出格式
对于每个测试用例,输出以下内容:
- 测试用例编号(从 111 开始)
- 机器人能够清理的最大垃圾单元格数量 NNN
- 达到该最大值的不同方式数量 CCC
- 一个有效的清理序列(包含 NNN 个单元格编号)
所有输出应在同一行,整数之间用空格分隔。
题目分析
问题本质
这个问题可以看作是在有向无环图(DAG\texttt{DAG}DAG)上寻找最长路径的问题,其中:
- 节点:包含垃圾的单元格
- 边:从节点 AAA 到节点 BBB 的边存在,当且仅当 BBB 在 AAA 的右下方(即 BBB 行 ≥A\geq A≥A 行 $ 且 BBB 列 ≥A\geq A≥A 列)
由于机器人只能向右或向下移动,因此有效的垃圾拾取序列必须满足:序列中每个后续垃圾单元格的行号和列号都不小于前一个单元格的行号和列号。
关键观察
-
移动限制:机器人只能向东或向南移动,这意味着有效的路径必须是单调非递减的(在行和列方向上)
-
问题转化:这类似于二维的最长递增子序列(LIS\texttt{LIS}LIS)问题,但约束条件是在两个维度上(行和列)都需要满足非递减关系
-
单元格编号:题目使用行优先顺序对单元格进行编号,第 iii 行第 jjj 列的单元格编号为 (i−1)×C+j(i-1) \times C + j(i−1)×C+j
解题思路
我们可以使用动态规划来解决这个问题:
-
预处理:
- 读取所有垃圾位置,计算每个垃圾的单元格编号
- 按行和列对垃圾进行排序(主要按行排序,行相同时按列排序)
-
动态规划状态设计:
- dp[i]dp[i]dp[i]:以第 iii 个垃圾结尾的序列能够清理的最大垃圾数量
- count[i]count[i]count[i]:达到 dp[i]dp[i]dp[i] 的不同路径数量
- prev[i]prev[i]prev[i]:记录最优路径中的前驱节点
-
状态转移:
对于每对垃圾 (i,j)(i, j)(i,j),其中 j<ij < ij<i:- 如果垃圾 jjj 在垃圾 iii 的左上方(即 garbage[j].row≤garbage[i].rowgarbage[j].row \leq garbage[i].rowgarbage[j].row≤garbage[i].row 且 garbage[j].col≤garbage[i].colgarbage[j].col \leq garbage[i].colgarbage[j].col≤garbage[i].col)
- 那么可以尝试用 dp[j]+1dp[j] + 1dp[j]+1 来更新 dp[i]dp[i]dp[i]
-
路径重建:
- 找到所有达到最大清理数量的终点
- 从任意一个终点开始,根据 prevprevprev 数组回溯重建一条完整路径
参考代码
// Robots(II)
// UVa ID: 10599
// Verdict: Accepted
// Submission Date: 2025-10-31
// UVa Run Time: 0.010s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <bits/stdc++.h>using namespace std;struct Cell {int row, col, index;Cell(int r, int c, int idx) : row(r), col(c), index(idx) {}bool operator<(const Cell& other) const {if (row != other.row) return row < other.row;return col < other.col;}
};int main() {cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);int R, C, testCase = 1;while (cin >> R >> C) {if (R == -1 && C == -1) break;vector<Cell> garbage;int r, c;// 读取垃圾位置while (cin >> r >> c) {if (r == 0 && c == 0) break;// 计算单元格编号:行优先顺序int index = (r - 1) * C + c;garbage.emplace_back(r, c, index);}// 如果没有垃圾if (garbage.empty()) {cout << "CASE#" << testCase++ << ": 0 0" << endl;continue;}// 按行和列排序sort(garbage.begin(), garbage.end());int n = garbage.size();vector<int> dp(n, 1); // dp[i]: 以第i个垃圾结尾的最大清理数量vector<long long> count(n, 1); // count[i]: 达到dp[i]的路径数量vector<int> prev(n, -1); // 前驱节点int maxClean = 1;// 动态规划for (int i = 0; i < n; i++) {for (int j = 0; j < i; j++) {// 检查是否可以转移:行和列都不递减if (garbage[j].row <= garbage[i].row && garbage[j].col <= garbage[i].col) {if (dp[j] + 1 > dp[i]) {dp[i] = dp[j] + 1;count[i] = count[j];prev[i] = j;} else if (dp[j] + 1 == dp[i]) {count[i] += count[j];}}}maxClean = max(maxClean, dp[i]);}// 计算总路径数和找到一条路径long long totalWays = 0;vector<int> path;for (int i = 0; i < n; i++) {if (dp[i] == maxClean) {totalWays += count[i];// 构建一条路径if (path.empty()) {int cur = i;while (cur != -1) {path.push_back(garbage[cur].index);cur = prev[cur];}reverse(path.begin(), path.end());}}}// 输出结果cout << "CASE#" << testCase++ << ": " << maxClean << " " << totalWays;for (int cell : path) cout << " " << cell;cout << '\n';}return 0;
}
