决策导航:第一步,看数据范围!
拿到题目,首先看 N, M(行、列)的范围:
-
稠密图 / 小棋盘 (Dense Grid)
- 特征:
N, M通常在3000以下,总格子数N * M在10^7以内。 - 决策:毫不犹豫,使用
g[N][N]二维数组。 这是最快、最直观、代码最简单的选择。 - 跳转至 -> 第一部分:稠密图模板
- 特征:
-
稀疏图 / 巨大坐标系 (Sparse Grid)
- 特征:
N, M非常大(例如 105,10910^5, 10^9105,109 甚至更大),但有意义的点(初始点c、操作数T)的数量是可控的(例如 105∼10610^5 \sim 10^6105∼106)。 - 决策:必须使用
map系列。 用g[N][N]会直接导致内存超限 (MLE) 或运行时错误 (RE)。 - 跳转至 -> 第二部分:稀疏图模板
- 特征:
第一部分:稠密图 / 小棋盘模板 (g[N][N])
A. 基础操作
const int N = 3005;
int g[N][N];
bool visited[N][N]; // 遍历时使用
// 1. 初始化
void init_dense() {
memset(g, 0, sizeof(g));
memset(visited, false, sizeof(visited));
}
// 2. 访问与修改
void modify_dense(int x, int y, int value) {
g[x][y] = value;
}
int read_dense(int x, int y) {
return g[x][y];
}
B. 遍历 (Traversal) - 核心
1. 广度优先搜索 (BFS) - 用于找最短路径(步数最少)
int dist[N][N];
// 方向数组: 上, 右, 下, 左 (4方向)
const int dx4[] = {-1, 0, 1, 0};
const int dy4[] = {0, 1, 0, -1};
// 方向数组: 8方向 (包括对角线)
const int dx8[] = {-1, -1, 0, 1, 1, 1, 0, -1};
const int dy8[] = {0, 1, 1, 1, 0, -1, -1, -1};
void bfs(int start_x, int start_y, int n, int m) {
memset(dist, -1, sizeof(dist)); // -1 表示未访问
memset(visited, false, sizeof(visited));
queue<pair<int, int>> q;
q.push({start_x, start_y});
visited[start_x][start_y] = true;
dist[start_x][start_y] = 0;
while (!q.empty()) {
pair<int, int> pos = q.front();
q.pop();
int x = pos.first;
int y = pos.second;
// 在这里处理当前点 (x, y) 的逻辑
// 遍历4个或8个方向
for (int i = 0; i < 4; ++i) { // 或 i < 8
int nx = x + dx4[i]; // 或 dx8[i]
int ny = y + dy4[i]; // 或 dy8[i]
// 检查边界
if (nx < 1 || nx > n || ny < 1 || ny > m) continue;
// 检查是否已访问
if (visited[nx][ny]) continue;
// 检查是否是障碍物等其他逻辑
// if (g[nx][ny] == OBSTACLE) continue;
visited[nx][ny] = true;
dist[nx][ny] = dist[x][y] + 1; // 更新距离
q.push({nx, ny});
}
}
}
2. 深度优先搜索 (DFS) - 用于查找所有可达点、连通分量
void dfs(int x, int y, int n, int m) {
visited[x][y] = true;
// 在这里处理当前点 (x, y) 的逻辑
for (int i = 0; i < 4; ++i) { // 或 i < 8
int nx = x + dx4[i]; // 或 dx8[i]
int ny = y + dy4[i]; // 或 dy8[i]
if (nx < 1 || nx > n || ny < 1 || ny > m) continue;
if (visited[nx][ny]) continue;
// if (g[nx][ny] == OBSTACLE) continue;
dfs(nx, ny, n, m);
}
}
第二部分:稀疏图 / 巨大坐标系模板 (map)
A. 核心数据结构
map<pair<int, int>, T> g; // 存储点的值
map<int, set<int>> rows; // 行号 -> 该行所有点的列号集合
map<int, set<int>> cols; // 列号 -> 该列所有点的行号集合
map<int, set<int>> diag1; // 对角线key: (y-x) -> x坐标集合
map<int, set<int>> diag2; // 反对角key: (y+x) -> x坐标集合
B. 基础操作 (增删改查)
// 添加点
void add_point(int x, int y, T value) {
g[{x, y}] = value;
rows[x].insert(y);
cols[y].insert(x);
diag1[y - x].insert(x);
diag2[y + x].insert(x);
}
// 删除点
void remove_point(int x, int y) {
g.erase({x, y});
if(rows.count(x)) rows[x].erase(y);
if(cols.count(y)) cols[y].erase(x);
if(diag1.count(y - x)) diag1[y - x].erase(x);
if(diag2.count(y + x)) diag2[y + x].erase(x);
}
// 读取点的值
T get_value(int x, int y) {
T default_value = //不存在点的默认值
if (g.count({x, y})) {
return g.at({x, y});
}
return default_value;
}
C. 邻居查找模板 (核心)
1. 查找行/列邻居
// 查找 (r, c) 上方的第一个点
pair<int, int> find_up(int r, int c) {
if (!cols.count(c)) return {0, 0};
auto& col_set = cols.at(c);
auto it = col_set.lower_bound(r);
return (it != col_set.begin()) ? make_pair(*prev(it), c) : make_pair(0, 0);
}
// 查找 (r, c) 下方的第一个点
pair<int, int> find_down(int r, int c) {
if (!cols.count(c)) return {0, 0};
auto& col_set = cols.at(c);
auto it = col_set.upper_bound(r);
return (it != col_set.end()) ? make_pair(*it, c) : make_pair(0, 0);
}
// 查找左/右与上/下逻辑相同,操作rows即可
pair<int, int> find_left(int r, int c) {
if (!rows.count(r)) return {0, 0};
auto& row_set = rows.at(r);
auto it = row_set.lower_bound(c);
return (it != row_set.begin()) ? make_pair(r, *prev(it)) : make_pair(0, 0);
}
pair<int, int> find_right(int r, int c) {
if (!rows.count(r)) return {0, 0};
auto& row_set = rows.at(r);
auto it = row_set.upper_bound(c);
return (it != row_set.end()) ? make_pair(r, *it) : make_pair(0, 0);
}
2. 查找对角线邻居
// 查找 (r, c) 左上方的第一个点
pair<int, int> find_up_left(int r, int c) {
int d_key = c - r;
if (!diag1.count(d_key)) return {0, 0};
auto& diag_set = diag1.at(d_key);
auto it = diag_set.lower_bound(r); // 对角线上的点用行号r或列号c排序均可
if (it == diag_set.begin()) return {0, 0};
int prev_r = *prev(it);
return {prev_r, prev_r + d_key}; // y = x + (y-x)
}
// 查找 (r, c) 右上方的第一个点
pair<int, int> find_up_right(int r, int c) {
int ad_key = c + r;
if (!diag2.count(ad_key)) return {0, 0};
auto& anti_diag_set = diag2.at(ad_key);
auto it = anti_diag_set.lower_bound(r);
if (it == anti_diag_set.begin()) return {0, 0};
int prev_r = *prev(it);
return {prev_r, ad_key - prev_r}; // y = (y+x) - x
}
// 查找 (r, c) 右下方的第一个点
Point find_down_right(int r, int c) {
int d_key = c - r;
if (!diag1.count(d_key)) return {0, 0};
auto& diag_set = diag1.at(d_key);
auto it = diag_set.upper_bound(r);
if (it == diag_set.end()) return {0, 0};
int next_r = *it;
return {next_r, next_r + d_key};
}
// 其他对角线方向以此类推...
D. 稀疏图遍历
类型1:稀疏点,但连接关系是稠密的(邻接)
void bfs_sparse(int start_x, int start_y) {
if (!g.count({start_x, start_y})) return; // 起点不存在
queue<pair<int, int>> q;
set<pair<int, int>> visited; // 稀疏图的 visited 也必须是稀疏的
q.push({start_x, start_y});
visited.insert({start_x, start_y});
while(!q.empty()){
pair<int, int> pos = q.front();
q.pop();
// 在这里处理点 pos 的逻辑
// 查找邻居的方式不再是 dx, dy
// 而是查询 map 中是否存在直接相邻的点
const int dx[] = {-1, 0, 1, 0};
const int dy[] = {0, 1, 0, -1};
for(int i=0; i<4; ++i){
int nx = pos.first + dx[i];
int ny = pos.second + dy[i];
// 检查该邻居是否存在于图中,并且没有被访问过
if(g.count({nx, ny}) && !visited.count({nx, ny})){
visited.insert({nx, ny});
q.push({nx, ny});
}
}
}
}
类型2:稀疏点,连接关系是跳跃的(行/列/对角线的下一个点)
// 场景:一个点可以“跳”到它所在行/列/对角线的下一个存在的点
// 比如激光在镜子间反射、棋子移动等
void bfs_sparse_line_of_sight(Point start_pos) {
if (!g.count(start_pos)) return;
queue<Point> q;
map<Point, int> dist; // 用 map 同时作为 visited 和 距离记录
q.push(start_pos);
dist[start_pos] = 0;
while(!q.empty()){
Point pos = q.front();
q.pop();
int r = pos.first;
int c = pos.second;
// 在这里处理点 pos 的逻辑,比如 if (pos == end_pos) return dist[pos];
// 将所有可能的“下一个邻居”找出
Point neighbors[8];
neighbors[0] = find_up(r, c);
neighbors[1] = find_down(r, c);
neighbors[2] = find_left(r, c);
neighbors[3] = find_right(r, c);
// neighbors[4] = find_up_left(r, c); ... (根据题目要求添加4或8方向)
// ...
for (const auto& next_pos : neighbors) {
// 检查邻居是否有效,并且没有被访问过
if (next_pos != INVALID_POS && !dist.count(next_pos)) {
dist[next_pos] = dist[pos] + 1;
q.push(next_pos);
}
}
}
}
1万+

被折叠的 条评论
为什么被折叠?



