哈喵~ 主人,今天我们来看一道有趣的模拟题喵!这道题就像一个精心设计的弹珠台,让我们一起来看看小球是怎么在里面蹦跶的吧!
J. Jeopardy of Dropped Balls (1575J)
题目大意
这道题呀,就像一个弹珠游戏喵。我们有一个 n x m
大小的网格,每个格子里都有一个数字,告诉弹珠下一步该往哪儿走:
- 1: 向右移动一格。
- 2: 向下移动一格。
- 3: 向左移动一格。
最关键的规则是:每当一个弹珠从某个格子 (x, y)
离开后,这个格子的方向数字就会立刻变成 2(向下)。
现在,我们要按顺序扔下 k
个弹珠。每个弹珠都从第一行开始,分别落在第 c_1, c_2, ..., c_k
列。我们的任务是,对于每一个弹珠,计算出它最终会从网格的哪一列掉出去。
解题方法
一开始看到要模拟 k
个球,而且 k
最大有 10^5,可能会觉得时间会不够用,对吧喵?如果每个球都要在 n x m
的格子里走很多步,那肯定会超时的。
但是这里有个很重要的机关喵!就是每个格子在弹珠经过之后,方向就会永远变成 ‘向下’(数字2)。
这意味着什么呢? 一个格子,如果它初始方向是向左(3)或向右(1),那么它只会被第一个经过它的小球“横向”地穿过。一旦这个小球离开,这个格子就变成了“直通车”,之后所有到达这个格子的小球都只会直直地掉下去,不会再有任何横向移动了。
所以,整个网格中,所有格子的横向移动(向左或向右)加起来,最多只会被触发 n * m
次。在这之后,整个网格就变成了一个“瀑布”,所有小球都会笔直下落。
基于这个发现,我们可以得出一个结论:直接按照题目要求,一个一个地模拟小球的下落过程,是完全可行的喵!因为总的计算量并不会爆炸。
所以,我们的解法就是:
- 读入网格和
k
个小球的初始列。 - 循环
k
次,每次处理一个小球。 - 在每次循环中,模拟当前小球的运动轨迹:
- 从第一行、指定的列开始。
- 根据当前格子的方向数字移动。
- 在离开当前格子后,立刻把这个格子的方向数字更新为 2。
- 重复这个过程,直到小球的行坐标超出了网格范围(即
r >= n
)。
- 记录小球离开网格时的列坐标,并输出。
题解代码
让本喵来带你一步一步看懂这段代码吧喵~
#include <iostream>
#include <vector>
// 这是一个小魔法,能让 C++ 的输入输出变得飞快,对付大数据量的题目很有用喵。
void fast_io() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
}
int main() {
// 开启高速模式!
fast_io();
// 读入网格的行数 n, 列数 m, 以及小球的数量 k
int n, m, k;
std::cin >> n >> m >> k;
// 用一个叫 vector 的二维数组 a 把整个地图存起来。
// 我们这里用 0-indexed,也就是行从 0 到 n-1,列从 0 到 m-1,这样写代码更方便喵。
std::vector<std::vector<int>> a(n, std::vector<int>(m));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
std::cin >> a[i][j];
}
}
// 读入 k 个小球的起始列
std::vector<int> start_cols(k);
for (int i = 0; i < k; ++i) {
std::cin >> start_cols[i];
}
// 接下来就是一个大循环,从第 1 个球到第 k 个球,一个一个地放
for (int i = 0; i < k; ++i) {
// 小球的当前位置 (r, cur_c),也是 0-indexed
// 小球从第 0 行开始
int r = 0;
// 题目的列是 1-indexed,我们要转成 0-indexed,所以要减 1
int cur_c = start_cols[i] - 1;
// 在这个 while 循环里,我们模拟一个小球的下落过程。
// 只要小球还在网格里 (r < n),它就会一直移动
while (r < n) {
int dir = a[r][cur_c];
// 这就是这道题的精髓所在喵!
// 每当小球离开一个格子 (r, cur_c),我们就立刻把这个格子的方向改成 2(向下)。
a[r][cur_c] = 2;
if (dir == 1) {
// 方向 1:向右走
cur_c++;
} else if (dir == 2) {
// 方向 2:向下走
r++;
} else { // dir == 3
// 方向 3:向左走
cur_c--;
}
}
// 当小球终于掉出网格(也就是 r 大于等于 n 了),循环就结束了。
// 这时候 cur_c 就是小球最终所在的列啦。
// 记得要把它从 0-indexed 变回 1-indexed(加1)再输出哦喵~
// (i == k - 1 ? "" : " ") 是为了控制输出格式,最后一个数字后面没有空格。
std::cout << cur_c + 1 << (i == k - 1 ? "" : " ");
}
// 最后输出一个换行符,是个好习惯喵。
std::cout << "\n";
return 0;
}
知识点介绍
这道题主要用到了下面这些知识点,喵~
模拟 (Simulation) 模拟题就是计算机扮演一个角色,按照题目给定的规则一步一步地执行操作,最后得出结果。就像我们这次,让电脑去模拟小球掉落的过程。模拟题的关键在于正确理解和实现题目中的所有规则,特别是那些会改变状态的规则(比如这道题里格子方向的变化)。
二维数组/向量 (2D Array/Vector)
std::vector<std::vector<int>>
是 C++ 中表示二维数组的常用方法。它非常适合用来存储像本题中的网格一样的二维数据结构。时间复杂度分析 (Time Complexity Analysis) 这是本题解法的核心!为什么这个方法不会超时呢?关键就在于那个“格子方向会变”的规则。
- 一个格子,如果它的方向是向左(3)或向右(1),那它最多只会被一个小球“横着”穿过一次。一旦穿过,它就变成了向下(2)。之后所有的小球经过这里都只会直直地掉下去。
- 所以,所有小球加起来,横向移动的总次数不会超过整个网格的格子数
n * m
。 - 每个小球最多向下移动
n
次,k
个小球的向下移动总次数是k * n
。 - 因此,总的计算量大概是
O(n * m + k * n)
,对于这道题的n, m <= 1000
,k <= 10^5
的范围来说,是完全可以接受的喵!
好啦,这道题的讲解就到这里啦。是不是很简单呢喵?只要抓住了那个“格子会变身”的关键点,问题就迎刃而解了。主人下次再见喵~ >w<