UVa 176 City Navigation
题目分析
本题描述了一种特殊的城市布局:街道(Street\texttt{Street}Street)和大道(Avenue\texttt{Avenue}Avenue)分别呈东西向和南北向,构成网格状结构。每条道路上的门牌号按照特定规律排列:
- 大道编号从西向东递增,街道编号从北向南递增
- 每个街区(
block)两侧各有 505050 个driveway,一侧编号为 00,02,…,9800, 02, \dots, 9800,02,…,98,另一侧为 01,03,…,9901, 03, \dots, 9901,03,…,99 - 当沿门牌号增加方向行驶时,奇数号在右侧
- 部分路段可能缺失(不连续),形成死胡同
题目要求:给定若干对地址(表示 driveway 的位置),计算两点间的最短合法路径长度,距离定义为经过的 driveway 数量(不包括起点和终点)。
合法驾驶规则:
- 靠右行驶
- 只能在交叉口穿越车道
- 进入或离开
driveway时必须右转 - 除非在死胡同尽头,否则禁止 U\texttt{U}U 形转弯
解题思路
1. 地址编码与状态表示
每个地址由三部分唯一标识:道路类型(A 或 S)、道路编号(000000 到 494949)、门牌号(000000000000 到 489948994899)。可以使用一个整数进行编码:
id=(code−A)×1000000+index×10000+numberid = (code - \texttt{A}) \times 1000000 + index \times 10000 + number id=(code−A)×1000000+index×10000+number
2. 缺失路段处理
输入中每个缺失路段给出道路标识和一段连续的门牌号范围(包含边界)。需要注意:缺失的是整个 section 边界上的道路段,因此如果 161216121612 不可通行,那么 161316131613 也不可通行。处理时将范围内的所有门牌号对应的 driveway 标记为不可用。
3. 图模型抽象
将每个 driveway 视为图中的一个节点。从一个节点移动到相邻节点的规则取决于:
- 当前所在道路的类型(
A或S) - 当前门牌号的奇偶性(决定行驶方向)
- 当前是否处于道路端点(门牌号为 000000、999999 等)
4. BFS\texttt{BFS}BFS 搜索
由于边权均为 111,使用 BFS\texttt{BFS}BFS 可以求出最短路径。从起点开始,每一步根据当前位置和方向生成后继节点,跳过被标记为缺失的节点。
5. 关键移动规则
以大道(Avenue\texttt{Avenue}Avenue)为例:
-
北向行驶(门牌号为偶数,即左侧
driveway):- 普通情况:向前移动到同一条大道的下一个
driveway(门牌号 −2-2−2) - 若该位置缺失,则只能进入当前街区对应的街道(右转)
- 在端点(门牌号 000000)时,可考虑右转、直行、左转三种选择
- 普通情况:向前移动到同一条大道的下一个
-
南向行驶(门牌号为奇数,即右侧
driveway):- 对称处理,门牌号 +2+2+2 向前移动
街道(Street\texttt{Street}Street)的移动规则类似,只是方向换为东西向。
6. U\texttt{U}U 形转弯处理
只有在死胡同末端(所有三个方向都缺失)时才允许 U\texttt{U}U 形转弯,即掉头沿原路返回。
代码实现
// City Navigation
// UVa ID: 176
// Verdict: Accepted
// Submission Date: 2016-02-26
// UVa Run Time: 0.338s
#include <bits/stdc++.h>
using namespace std;
// 存储一个 driveway 的结构体
struct house {
char code; // 'A' 或 'S'
int index; // 道路编号 00~49
int number; // 门牌号 0000~4899
int step; // BFS 步数
};
set<int> missing; // 缺失的 driveway 集合
set<int> discovered; // BFS 已访问集合
house from, to; // 起点和终点
// 将 driveway 编码为唯一整数
int getHouseId(house h) {
return (h.code - 'A') * 1000000 + h.index * 10000 + h.number;
}
// 判断 driveway 是否缺失
bool isMissing(house h) {
return missing.count(getHouseId(h)) > 0;
}
// 判断是否已访问
bool isVisited(house h) {
return discovered.count(getHouseId(h)) > 0;
}
// 创建 driveway 对象
house makeHouse(char code, int index, int number, int step) {
return (house){code, index, number, step};
}
// BFS 搜索最短路径
void bfs() {
queue<house> next;
next.push(from);
discovered.clear();
discovered.insert(getHouseId(from));
while (next.empty() == false) {
house v = next.front();
next.pop();
// 到达终点,输出步数(减去起点和终点)
if (v.number == to.number && v.index == to.index && v.code == to.code) {
cout << (v.step - 2) << "\n";
break;
}
// 当前在大道上(南北向)
if (v.code == 'A') {
int sNumber = v.number / 100; // 对应的街道编号
int houseNumber = v.number % 100; // 门牌号后两位
// 北向行驶(偶数门牌号,位于左侧)
if (houseNumber % 2 == 0) {
if (houseNumber == 0) { // 到达北端点
bool nextFound = false;
vector<house> temp;
// 右转进入街道(东向)
if (v.index > 0)
temp.push_back(makeHouse('S', sNumber, (v.index - 1) * 100 + 98, v.step + 1));
// 直行继续向北(上一条街道)
if (sNumber > 0)
temp.push_back(makeHouse('A', v.index, (sNumber - 1) * 100 + 98, v.step + 1));
// 左转进入街道(西向)
if (v.index < 49)
temp.push_back(makeHouse('S', sNumber, v.index * 100 + 1, v.step + 1));
for (int i = 0; i < temp.size(); i++) {
if (isMissing(temp[i]) == false) {
if (isVisited(temp[i]) == false) {
discovered.insert(getHouseId(temp[i]));
next.push(temp[i]);
}
nextFound = true;
}
}
// 所有方向都缺失 -> U 形转弯
if (nextFound == false) {
house tempHouse = makeHouse('A', v.index, v.number + 1, v.step + 1);
if (isVisited(tempHouse) == false) {
discovered.insert(getHouseId(tempHouse));
next.push(tempHouse);
}
}
} else { // 非端点情况
house temp = makeHouse('A', v.index, v.number - 2, v.step + 1);
if (isMissing(temp)) // 前方缺失,只能右转进入街道
temp = makeHouse('A', v.index, v.number + 1, v.step + 1);
if (isVisited(temp) == false) {
discovered.insert(getHouseId(temp));
next.push(temp);
}
}
}
// 南向行驶(奇数门牌号,位于右侧)对称处理
if (houseNumber % 2 == 1) {
if (houseNumber == 99) { // 到达南端点
bool nextFound = false;
vector<house> temp;
// 右转进入街道(西向)
if (v.index < 49)
temp.push_back(makeHouse('S', sNumber + 1, v.index * 100 + 1, v.step + 1));
// 直行继续向南(下一条街道)
if (sNumber < 48)
temp.push_back(makeHouse('A', v.index, (sNumber + 1) * 100 + 1, v.step + 1));
// 左转进入街道(东向)
if (v.index > 0)
temp.push_back(makeHouse('S', sNumber + 1, (v.index - 1) * 100 + 98, v.step + 1));
for (int i = 0; i < temp.size(); i++) {
if (isMissing(temp[i]) == false) {
if (isVisited(temp[i]) == false) {
discovered.insert(getHouseId(temp[i]));
next.push(temp[i]);
}
nextFound = true;
}
}
// U 形转弯
if (nextFound == false) {
house tempHouse = makeHouse('A', v.index, v.number - 1, v.step + 1);
if (isVisited(tempHouse) == false) {
discovered.insert(getHouseId(tempHouse));
next.push(tempHouse);
}
}
} else {
house temp = makeHouse('A', v.index, v.number + 2, v.step + 1);
if (isMissing(temp))
temp = makeHouse('A', v.index, v.number - 1, v.step + 1);
if (isVisited(temp) == false) {
discovered.insert(getHouseId(temp));
next.push(temp);
}
}
}
}
// 当前在街道上(东西向)处理逻辑类似
if (v.code == 'S') {
int aNumber = v.number / 100; // 对应的大道编号
int houseNumber = v.number % 100;
// 东向行驶(偶数门牌号)
if (houseNumber % 2 == 0) {
if (houseNumber == 0) { // 东端点
bool nextFound = false;
vector<house> temp;
// 右转进入大道(南向)
if (v.index < 49)
temp.push_back(makeHouse('A', aNumber, v.index * 100 + 1, v.step + 1));
// 直行继续向东(上一条大道)
if (aNumber > 0)
temp.push_back(makeHouse('S', v.index, (aNumber - 1) * 100 + 98, v.step + 1));
// 左转进入大道(北向)
if (v.index > 0)
temp.push_back(makeHouse('A', aNumber, (v.index - 1) * 100 + 98, v.step + 1));
for (int i = 0; i < temp.size(); i++) {
if (isMissing(temp[i]) == false) {
if (isVisited(temp[i]) == false) {
discovered.insert(getHouseId(temp[i]));
next.push(temp[i]);
}
nextFound = true;
}
}
// U 形转弯
if (nextFound == false) {
house tempHouse = makeHouse('S', v.index, v.number + 1, v.step + 1);
if (isVisited(tempHouse) == false) {
discovered.insert(getHouseId(tempHouse));
next.push(tempHouse);
}
}
} else {
house temp = makeHouse('S', v.index, v.number - 2, v.step + 1);
if (isMissing(temp))
temp = makeHouse('S', v.index, v.number + 1, v.step + 1);
if (isVisited(temp) == false) {
discovered.insert(getHouseId(temp));
next.push(temp);
}
}
}
// 西向行驶(奇数门牌号)
if (houseNumber % 2 == 1) {
if (houseNumber == 99) { // 西端点
bool nextFound = false;
vector<house> temp;
// 右转进入大道(北向)
if (v.index > 0)
temp.push_back(makeHouse('A', aNumber + 1, (v.index - 1) * 100 + 98, v.step + 1));
// 直行继续向西(下一条大道)
if (aNumber < 48)
temp.push_back(makeHouse('S', v.index, (aNumber + 1) * 100 + 1, v.step + 1));
// 左转进入大道(南向)
if (v.index < 49)
temp.push_back(makeHouse('A', aNumber + 1, v.index * 100 + 1, v.step + 1));
for (int i = 0; i < temp.size(); i++) {
if (isMissing(temp[i]) == false) {
if (isVisited(temp[i]) == false) {
discovered.insert(getHouseId(temp[i]));
next.push(temp[i]);
}
nextFound = true;
}
}
// U 形转弯
if (nextFound == false) {
house tempHouse = makeHouse('S', v.index, v.number - 1, v.step + 1);
if (isVisited(tempHouse) == false) {
discovered.insert(getHouseId(tempHouse));
next.push(tempHouse);
}
}
} else {
house temp = makeHouse('S', v.index, v.number + 2, v.step + 1);
if (isMissing(temp))
temp = makeHouse('S', v.index, v.number - 1, v.step + 1);
if (isVisited(temp) == false) {
discovered.insert(getHouseId(temp));
next.push(temp);
}
}
}
}
}
}
int main(int argc, char *argv[]) {
cin.tie(0);
cout.sync_with_stdio(false);
string line;
int start, end;
// 读取缺失路段部分,直到 '#'
while (cin >> line, line != "#") {
cin >> start >> end;
// 标记该路段上连续的门牌号为缺失
for (int i = start; i <= end + 1; i++) {
house temp = (house){line.front(), stoi(line.substr(1)), i, 0};
missing.insert(getHouseId(temp));
}
}
// 读取地址对部分,直到 '#'
while (cin >> line, line != "#") {
cin >> start;
from = (house){line.front(), stoi(line.substr(1)), start, 1};
cin >> line >> end;
to = (house){line.front(), stoi(line.substr(1)), end, 0};
bfs(); // 对每一对地址进行 BFS 搜索
}
return 0;
}
总结
本题的核心在于将城市道路网络转化为图模型,并模拟靠右行驶的交通规则。BFS\texttt{BFS}BFS 保证找到最短路径,关键在于正确处理各种移动情况:直行、左转、右转以及死胡同中的 U\texttt{U}U 形转弯。理解门牌号与行驶方向的关系是解决问题的前提。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)