总时间限制: 2000ms 内存限制: 65536kB
描述
传说很遥远的藏宝楼顶层藏着诱人的宝藏。小明历尽千辛万苦终于找到传说中的这个藏 宝楼,藏宝楼的门口竖着一个木板,上面写有几个大字:寻宝说明书。说明书的内容如下:
藏宝楼共有 N+1 层,最上面一层是顶层,顶层有一个房间里面藏着宝藏。除了顶层外,藏宝楼另有 N 层,每层 M 个房间,这 M 个房间围成一圈并按逆时针方向依次编号为 0,…, M-1。其中一些房间有通往上一层的楼梯,每层楼的楼梯设计可能不同。每个房间里有一个指示牌,指示牌上有一个数字 x,表示从这个房间开始按逆时针方向选择第 x 个有楼梯的房间(假定该房间的编号为 k),从该房间上楼,上楼后到达上一层的 k 号房间。比如当前房间的指示牌上写着 2,则按逆时针方向开始尝试,找到第 2 个有楼梯的房间,从该房间上楼。如果当前房间本身就有楼梯通向上层,该房间作为第一个有楼梯的房间。
寻宝说明书的最后用红色大号字体写着:“寻宝须知:帮助你找到每层上楼房间的指示牌上的数字(即每层第一个进入的房间内指示牌上的数字)总和为打开宝箱的密钥”。
请帮助小明算出这个打开宝箱的密钥。
输入
第一行 2 个整数 N 和 M,之间用一个空格隔开。N 表示除了顶层外藏宝楼共 N 层楼, M 表示除顶层外每层楼有 M 个房间。
接下来 N*M 行,每行两个整数,之间用一个空格隔开,每行描述一个房间内的情况,其中第(i-1)*M+j 行表示第 i 层 j-1 号房间的情况(i=1, 2, …, N;j=1, 2, … ,M)。第一个整数表示该房间是否有楼梯通往上一层(0 表示没有,1 表示有),第二个整数表示指示牌上的数字。注意,从 j 号房间的楼梯爬到上一层到达的房间一定也是 j 号房间。
最后一行,一个整数,表示小明从藏宝楼底层的几号房间进入开始寻宝(注:房间编号从 0 开始)。
对于50%数据,有 0< N ≤ 1000,0 < x ≤ 10000;
对于100%数据,有 0 < N ≤ 10000,0 < M ≤ 100,0 < x ≤ 1,000,000。
输出
输出只有一行,一个整数,表示打开宝箱的密钥,这个数可能会很大,请输出对 20123 取模的结果即可。
样例输入
2 3 1 2 0 3 1 4 0 1 1 5 1 2 1
样例输出
5
提示
输入输出样例说明:
第一层:
0 号房间,有楼梯通往上层,指示牌上的数字是 2;
1 号房间,无楼梯通往上层,指示牌上的数字是 3;
2 号房间,有楼梯通往上层,指示牌上的数字是 4;
第二层:
0 号房间,无楼梯通往上层,指示牌上的数字是 1;
1 号房间,有楼梯通往上层,指示牌上的数字是 5;
2 号房间,有楼梯通往上层,指示牌上的数字是 2;
小明首先进入第一层(底层)的 1 号房间,记下指示牌上的数字为 3,然后从这个房间 开始,沿逆时针方向选择第 3 个有楼梯的房间 2 号房间进入,上楼后到达第二层的 2 号房间, 记下指示牌上的数字为 2,由于当前房间本身有楼梯通向上层,该房间作为第一个有楼梯的房间。因此,此时沿逆时针方向选择第 2 个有楼梯的房间即为 1 号房间,进入后上楼梯到达 顶层。这时把上述记下的指示牌上的数字加起来,即 3+2=5,所以打开宝箱的密钥就是 5。
【过程模拟】
假设有3层楼,每层有4个房间。以下表格模拟了小明在每一层根据约瑟夫环规则找到楼梯并累加指示牌数字的过程。
输入数据:
楼层数 n: 3
每层房间数 m: 4
房间信息(是否有楼梯和指示牌数字,1表示有楼梯,0表示没有,后面是指示牌数字):
1层: 1 3, 0 5, 0 2, 1 4
2层: 0 6, 1 1, 0 7, 1 3
3层: 1 8, 0 9, 1 2, 0 1
初始房间号 startRoom: 2(从1开始编号)
模拟过程表格:
当前楼层 | 当前房间(从1开始编号) | 是否有楼梯 | 指示牌数字 | 累加指示牌数字 | 需要走过的房间数 | 下一个有楼梯的房间号(从1开始编号) |
---|---|---|---|---|---|---|
1 | 2 | 0 | 5 | 5 | 5 % 2 = 1 | 3 |
(经过1个房间) | ||||||
1 | 3 | 0 | 2 | 5 + 2 = 7 | 7 % 2 = 1 | 4 |
(经过1个房间) | ||||||
1 | 4 | 1 | 4 | 7 + 4 = 11 | - | - |
2 | 1 | 0 | 6 | 11 + 6 = 17 | 17 % 2 = 1 | 2 |
(经过1个房间) | ||||||
2 | 2 | 1 | 1 | 17 + 1 = 18 | - | - |
3 | 1 | 1 | 8 | 18 + 8 = 26 | 26 % 3 = 2 | 3 |
(经过2个房间) | ||||||
3 | 3 | 1 | 2 | 26 + 2 = 28 | - | - |
最终输出:
密钥(累加指示牌数字 % 20123):28
【解题思路】
这道题可以被看作是一个多层约瑟夫环(Josephus problem)问题的变种。
约瑟夫环问题是一个著名的数学和计算机科学问题,通常描述为:编号为1到n的n个人按顺时针方向围成一圈,从编号为1的人开始,按顺时针方向每数到第m个人就将其杀死,然后再由下一个人重新开始计数,直到存活下来最后一个人。对于给定的n和m,要求找出最后存活的人的编号。
在这道题目中,每一层楼都可以被看作是一个单独的约瑟夫环。小明在每一层楼都需要根据房间的指示走过一定数量的房间(这相当于约瑟夫环中的“数到第m个人”),并且如果房间有楼梯,他还需要额外考虑这个因素(这可以看作是约瑟夫环中“杀死”一个人的操作,即小明通过该房间并继续他的路径)。
题目的关键在于,每一层楼都是一个独立的约瑟夫环,而且每一层楼的起始房间、房间数量、以及“杀死”的条件(即房间是否有楼梯)都可能不同。小明需要逐层解决这些约瑟夫环问题,最终得到打开宝箱的密钥。
-
数据结构和变量定义:
r[10001][101][2]
:三维数组,用于存储每层每个房间的信息。其中,r[i][j][0]
表示第i
层第j
个房间是否有楼梯(0 表示没有,1 表示有),r[i][j][1]
表示该房间的指示牌上的数字。a[10001]
:数组,用于存储每层有楼梯的房间数量。n, m
:分别表示楼层数和每层的房间数。s
:表示小明最初进入的房间编号。ans
:用于累加每层小明所经过房间的指示牌数字,并最终得到打开宝箱的密钥。
-
输入处理:
- 首先读入楼层数
n
和房间数m
。 - 然后通过两层循环读入每个房间的信息,并统计每层有楼梯的房间数量,存储在数组
a
中。 - 最后读入小明最初进入的房间编号
s
。
- 首先读入楼层数
-
模拟过程:
- 通过一层循环遍历每一层楼。
- 累加当前房间的指示牌数字到
ans
中,并取模20123
以避免整数溢出。 - 计算小明在当前层需要走过的房间数
s1
,这是通过取当前房间的指示牌数字对当层有楼梯的房间数a[i]
的余数得到的。这里使用取余是为了优化,防止s1
过大导致后面的循环次数过多而超时。 - 如果
s1
为 0,则特判为a[i]
,因为根据题目描述,如果小明需要走过的房间数恰好等于当层有楼梯的房间数,则他会回到最初的房间并继续上楼。 - 如果当前房间本身有楼梯,则
s1
需要减 1,因为小明不需要再额外走过一个房间。 - 使用一个
while
循环模拟小明在当前层走过房间的过程,直到s1
减为 0。这个循环实际上是在模拟一个约瑟夫环问题,即在一个环形结构中按照一定规则删除元素的过程。- 在循环中,不断增加
s
的值以模拟小明走过房间,如果s
等于m
,则将其重置为 0,以实现环形结构。 - 如果当前房间有楼梯,则
s1
减 1,表示小明已经走过了一个需要计算在内的房间。
- 在循环中,不断增加
- 累加当前房间的指示牌数字到
- 通过一层循环遍历每一层楼。
-
输出结果:
- 循环结束后,输出累加得到的密钥
ans
。
- 循环结束后,输出累加得到的密钥
【关键点】
关键点:
-
数据结构的选择:题目中涉及到多层楼和多个房间的信息,因此需要选择合适的数据结构来存储这些信息。在这个问题中,使用了一个三维数组
r
来存储每个房间的信息(是否有楼梯和指示牌上的数字),以及一个一维数组a
来存储每层有楼梯的房间数。 -
约瑟夫环的理解:每一层楼都可以看作是一个约瑟夫环问题,需要模拟小明在环中的移动过程,直到满足特定的条件(即走过一定数量的房间)。理解约瑟夫环的解决方法是解题的关键。
-
取余操作的应用:为了防止在计算过程中整数溢出或者减少循环次数以提高效率,题目中使用了取余操作。特别是在计算小明需要走过的房间数时,通过取余操作可以优化计算过程。
-
累加指示牌数字:在模拟过程中,需要累加每层小明所经过房间的指示牌数字,并最终得到打开宝箱的密钥。这个累加过程需要注意取模操作,以确保结果在题目要求的范围内。
注意的地方:
-
输入输出的处理:在读取输入和输出最终结果时,需要确保数据的准确性和格式的正确性。特别是当数据范围较大时,要注意使用合适的数据类型来存储和处理数据。
-
边界条件的判断:在模拟过程中,会遇到各种边界条件,如房间编号超出范围、需要走过的房间数为0等。这些情况都需要进行特殊处理,以确保程序的正确运行。
-
时间复杂度的控制:由于题目中涉及到多层楼和多个房间的模拟过程,如果处理不当可能会导致时间复杂度过高而超时。因此,在编写代码时需要注意优化算法,减少不必要的循环和计算。
【代码实现】
#include<iostream>
using namespace std;
// 定义楼层信息数组和每层有楼梯的房间数数组
int r[10001][101][2], a[10001]; // r[楼层][房间号][0:是否有楼梯, 1:指示牌数字], a[楼层]: 该层有楼梯的房间数
int main() {
int n, m, s, ans = 0, start;
cin >> n >> m; // 读入楼层数n和每层的房间数m
// 初始化楼层信息,并统计每层有楼梯的房间数
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> r[i][j][0] >> r[i][j][1]; // 读入每个房间的信息
if (r[i][j][0] == 1) {
++a[i]; // 如果该房间有楼梯,则增加该层的楼梯房间数
}
}
}
cin >> s; // 读入小明最初进入的房间编号
// 模拟小明在每层的行动,并累加指示牌上的数字
for (int i = 1; i <= n; ++i) {
ans = (r[i][s][1] + ans) % 20123; // 累加指示牌上的数字,并取模防止溢出
// 计算小明在该层需要走过的房间数
int s1 = r[i][s][1] % a[i]; // 使用取余优化,防止s1过大导致超时
if (s1 == 0) { // 特判:如果s1为0,则设置为该层的楼梯房间数
s1 = a[i];
}
if (r[i][s][0] == 1) { // 如果当前房间有楼梯,则s1减1
--s1;
}
// 约瑟夫环:模拟小明在该层走过房间的过程
while (s1 != 0) {
++s; // 走到下一个房间
if (s == m) { // 如果走到最后一个房间,则回到第一个房间
s = 0;
}
if (r[i][s][0] == 1) { // 如果当前房间有楼梯,则s1减1
--s1;
}
}
}
cout << ans; // 输出最终的密钥
return 0;
}