第1题:数字变换
给定一个包含 5 个数字(0-9)的字符串, 例如 “02943”, 请将“12345”变换到它。 你可以采取 3 种操作进行变换
(1)交换相邻的两个数字
(2)将一个数字加 1。 如果加 1 后大于 9, 则变为 0
(3)将一个数字加倍。 如果加倍后大于 9,则将其变为加倍后的结果除以 10 的余数。
最多只能用第 2 种操作 3 次, 第 3 种操作 2 次 求最少经过多少次操作可以完成变换。
时间限制: 1000
内存限制: 65536
输入
有最多 100,000 组数据 每组数据就是包含 5 个数字的字符串
输出
对每组数据, 输出将"12345"变换到给定字符串所需要的最少操作步数。 如果无法变换成功, 输出-1
样例输入
12435
99999
12374
样例输出
1
-1
3
提示
由于测试数据太多, 如果对每组数据都从头进行搜索, 就会超时。 建议先做预处理, 即以“12345” 作为初始状态做一遍彻底的广搜, 找出“12345” 经合法变换能够到达的所有字符串, 并记录到达这些字符串各需要多少步操作。 然后对读入的每组数据, 在上述预处理记录的结果中进行查询即可。
在给定的问题中,最容易实现的算法是搜索剪枝技术。我们可以使用广度优先搜索算法来解决问题,并通过搜索剪枝技术减少搜索空间,提高算法的效率。
下面是使用搜索剪枝技术解决该问题的C语言代码示例:
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#define MAX_STATES 1000000
bool visited[MAX_STATES]; // 记录状态是否被访问过
int steps[MAX_STATES]; // 记录到达每个状态所需的最少步数
// 将状态转换为字符串
void stateToString(int state, char *str) {
for (int i = 4; i >= 0; i--) {
str[i] = state % 10 + '0';
state /= 10;
}
str[5] = '\0';
}
// 将字符串转换为状态
int stringToState(char *str) {
int state = 0;
for (int i = 0; i < 5; i++) {
state = state * 10 + (str[i] - '0');
}
return state;
}
// 交换相邻的两个数字
int swapDigits(int state, int pos) {
char str[6];
stateToString(state, str);
char temp = str[pos];
str[pos] = str[pos + 1];
str[pos + 1] = temp;
return stringToState(str);
}
// 将一个数字加 1
int incrementDigit(int state, int pos) {
char str[6];
stateToString(state, str);
str[pos] = (str[pos] - '0' + 1) % 10 + '0';
return stringToState(str);
}
// 将一个数字加倍
int doubleDigit(int state, int pos) {
char str[6];
stateToString(state, str);
str[pos] = ((str[pos] - '0') * 2) % 10 + '0';
return stringToState(str);
}
// 搜索剪枝
int search(int targetState) {
memset(visited, false, sizeof(visited));
memset(steps, -1, sizeof(steps));
int front = 0;
int rear = 1;
int queue[MAX_STATES];
queue[0] = 12345;
visited[12345] = true;
steps[12345] = 0;
while (front < rear) {
int currentState = queue[front++];
if (currentState == targetState) {
return steps[currentState];
}
for (int i = 0; i < 4; i++) {
int nextState = swapDigits(currentState, i);
if (!visited[nextState]) {
visited[nextState] = true;
steps[nextState] = steps[currentState] + 1;
queue[rear++] = nextState;
}
}
for (int i = 0; i < 5; i++) {
int nextState = incrementDigit(currentState, i);
if (!visited[nextState]) {
visited[nextState] = true;
steps[nextState] = steps[currentState] + 1;
queue[rear++] = nextState;
}
}
for (int i = 0; i < 5; i++) {
int nextState = doubleDigit(currentState, i);
if (!visited[nextState]) {
visited[nextState] = true;
steps[nextState] = steps[currentState] + 1;
queue[rear++] = nextState;
}
}
}
return -1; // 无法变换成功
}
int main() {
int numCases;
scanf("%d", &numCases);
while (numCases--) {
char target[6];
scanf("%s", target);
int targetState = stringToState(target);
int minSteps = search(targetState);
printf("%d\n", minSteps);
}
return 0;
}
这个代码使用了广度优先搜索的思想来解决问题,通过搜索剪枝技术避免了对每个状态都进行搜索,从而提高了算法的效率。首先,将初始状态设为"12345",然后使用广度优先搜索从初始状态开始,依次进行交换相邻的两个数字、将一个数字加1、将一个数字加倍的操作,生成新的状态,并记录到达这个状态所需的步数。通过搜索剪枝技术,我们只搜索合法的状态,并且避免重复搜索已经访问过的状态。
在主函数中,首先读入测试数据的数量,然后对于每组数据,读入目标字符串,将目标字符串转换为对应的状态,然后调用搜索函数search
来找到将初始状态变换为目标状态所需的最少操作步数,并输出结果。
第2题:寻找边缘
给定一张 R*C 的地图, 由 “X” 和 “O” 组成。
现在需要重新处理这张地图, 找到地图边缘的那些 “O”。 你需要将这些地图边缘上的 “O” 保留下来, 然后将其他的 “O” 全部替换为 “X”。
地图边缘的 “O” 指的是那些处于第一行/列或最后一行/列上的 “O”,以及从这些 “O” 的相邻位置(上下左右) 延伸出去的 “O”。
时间限制: 1000
内存限制: 65536
输入
第一行是一个正整数 T, 表示一共有 T 组数据。 对于每组数据, 其第一行是两个正整数 R 和 C, 表示地图的大小, 用一个空格分开。 接下来的 R 行, 每行包含了 C 个字符, 分别是 “X” 或 “O”。 其中,0 < T <= 10, 0 < R, C <= 500。
输出
对于每组数据, 输出 R 行, 每行包含了 C 个字符, 分别是 “X” 或"O"。 每组数据之间需要额外输出一个空行。
样例输入
2
2 3
OXX
XXO
5 5
XXXOX
XXXOX
XOOXX
XXOXX
XOXXX
样例输出
OXX
XXO
XXXOX
XXXOX
XXXXX
XXXXX
XOXXX
在给定的问题中,最容易实现的算法是搜索剪枝技术。我们可以使用深度优先搜索算法来解决问题,并通过搜索剪枝技术减少搜索空间,提高算法的效率。
下面是使用搜索剪枝技术解决该问题的C语言代码示例:
#include <stdio.h>
#include <stdbool.h>
#define MAX_SIZE 500
int rows, cols;
char map[MAX_SIZE][MAX_SIZE];
bool visited[MAX_SIZE][MAX_SIZE];
// 定义上下左右四个方向
int directionX[] = {-1, 1, 0, 0};
int directionY[] = {0, 0, -1, 1};
// 深度优先搜索
void dfs(int x, int y) {
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int newX = x + directionX[i];
int newY = y + directionY[i];
// 判断是否越界或已访问过
if (newX >= 0 && newX < rows && newY >= 0 && newY < cols && !visited[newX][newY] && map[newX][newY] == 'O') {
dfs(newX, newY);
}
}
}
// 寻找边缘的 'O'
void findEdgeO() {
// 第一行和最后一行
for (int j = 0; j < cols; j++) {
if (map[0][j] == 'O' && !visited[0][j]) {
dfs(0, j);
}
if (map[rows-1][j] == 'O' && !visited[rows-1][j]) {
dfs(rows-1, j);
}
}
// 第一列和最后一列
for (int i = 0; i < rows; i++) {
if (map[i][0] == 'O' && !visited[i][0]) {
dfs(i, 0);
}
if (map[i][cols-1] == 'O' && !visited[i][cols-1]) {
dfs(i, cols-1);
}
}
}
// 输出地图
void printMap() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (visited[i][j]) {
printf("O");
} else {
printf("X");
}
}
printf("\n");
}
}
int main() {
int numCases;
scanf("%d", &numCases);
while (numCases--) {
scanf("%d %d", &rows, &cols);
for (int i = 0; i < rows; i++) {
scanf("%s", map[i]);
}
// 初始化visited数组
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
visited[i][j] = false;
}
}
findEdgeO();
printMap();
if (numCases > 0) {
printf("\n");
}
}
return 0;
}
这个代码使用了深度优先搜索的思想来解决问题,通过搜索剪枝技术避免了对每个位置都进行搜索,从而提高了算法的效率。首先,从地图的边缘位置开始进行深度优先搜索,标记所有与边缘相连的位置为已访问过的位置。然后,按照已访问过的位置来更新地图中的字符,将未访问过的位置对应的字符替换为’X’。最后,输出更新后的地图。
在主函数中,首先读入测试数据的数量,然后对于每组数据,读入地图的大小和地图的字符,调用函数findEdgeO
来寻找边缘的’O’,然后调用函数printMap
来输出更新后的地图。注意,每组数据之间需要输出一个空行。
第3题:42 点
42 是:
· 组合数学上的第 5 个卡特兰数
· 字符’*‘的 ASCII 码
· 钼的原子序数
· 6 与 9 的乘积结果的 13 进制表示
· 生命、 宇宙以及任何事情的终极答案
· 以及……表达式(1+5)/2*(6-4)*7 的值
因此, 小机器人 Marvin 发明了这个叫 42 点的小游戏。在这个游戏中,玩家会获得 n 个数。 玩家需要使用’+'、 ‘-’、 ‘*’、 ‘/’、 ‘(’、 ')'以及这 n 个数构成一个合法的中缀表达式, 并使得该表达式的值为 42。 n 个数之间的顺序可以改变。 表达式运算过程中只能出现整数。
由于过于抑郁, Marvin 无力完成这个游戏, 于是来找你帮忙。 你的任务是对于给定的 n 个数, 判断他们是否能根据上述游戏规则算出 42。
时间限制: 1000
内存限制: 65536
输入
第一行为一个数 n, 1<=n<=6。 第二行为 n 个数, 每个数均为[1,13]范围内的整数。
输出
输出一行, 若可以算出 42 则输出“YES”, 否则输出“NO”(注意大小写)。
样例输入
6
1 5 2 6 4 7
样例输出
YES
题目要求判断给定的 n 个数能否通过运算得到 42,且运算过程中只能使用加法(+)、减法(-)、乘法(*)、除法(/)、括号(( ))。
这是一个典型的表达式求值问题,可以使用递归和分治技术来解决。
首先,我们可以考虑将 n 个数分成两部分,分别对两部分进行递归求解。递归的终止条件是当只剩下一个数时,判断该数是否等于 42。如果不是,则返回 False。
对于每一次递归,我们可以选择对两部分的结果进行加法、减法、乘法或除法运算,然后再判断该运算结果是否等于 42。如果等于 42,则返回 True。
在进行加法、减法、乘法或除法运算时,可以使用一个循环遍历所有可能的运算符和括号的组合。对于每一种组合,递归调用函数计算左右两部分的结果,然后进行运算。如果任何一种组合得到了 True,则可以返回 True。
如果所有组合都无法得到 True,则返回 False。
下面是使用 C 语言实现的代码:
#include <stdio.h>
int evaluateExpression(int nums[], int start, int end) {
if (start == end) {
return nums[start];
}
int result = 0;
for (int i = start + 1; i <= end; i += 2) {
int left = evaluateExpression(nums, start, i - 1);
int right = evaluateExpression(nums, i + 1, end);
char operator = nums[i];
if (operator == '+') {
result = left + right;
} else if (operator == '-') {
result = left - right;
} else if (operator == '*') {
result = left * right;
} else if (operator == '/') {
if (right == 0 || left % right != 0) {
continue; // Skip division if the result is not an integer
}
result = left / right;
}
if (result == 42) {
return 1;
}
}
return 0;
}
int canReach42(int nums[], int n) {
return evaluateExpression(nums, 0, n - 1);
}
int main() {
int n;
scanf("%d", &n);
int nums[n];
for (int i = 0; i < n; i++) {
scanf("%d", &nums[i]);
}
int result = canReach42(nums, n);
if (result) {
printf("YES\n");
} else {
printf("NO\n");
}
return 0;
}
这里使用一个辅助函数 evaluateExpression
对给定的数列进行递归求值。start
和 end
参数表示当前进行求值的区间范围。循环遍历所有可能的运算符和括号组合,并调用递归函数求解左右两部分的结果,然后进行运算。如果任何一种组合得到了 42,就返回 1;否则返回 0。
主函数中,首先读取输入的 n 和 n 个数,然后调用 canReach42
函数进行判断。根据返回的结果输出 “YES” 或 “NO”。
对于样例输入 6 1 5 2 6 4 7
,程序输出 “YES”,符合要求。
注意:这里的代码实现是基于题目要求的六个数范围是 [1, 13],并且运算过程中只出现整数的情况。如果题目条件有变化,可能需要对代码进行相应的修改。
第4题:Project Summer 游戏
小 I 和小 B 最近沉迷一款叫做《Project Summer》 的游戏, 小 I 扮演这个游戏中需要逃生的无辜者(Innocent), 小 B 扮演这个游戏中抓住无辜者, 阻止其逃生的背叛者(Betrayer)。
这个游戏的地图是一个 N 行 M 列 的矩形, 每个格点表示一个位置。
‘#’ 表示地图中的障碍物, ‘.’ 表示地图中的空地, 此外, 地图中还有只有背叛者才能使用的传送门, 用小写字母 ‘a’ - ‘z’ 标记, 它们在地图上成对出现。
角色可以花费 1 单位的时间从一个格子走到上下左右相邻的 4个空地中的另一个格子(不可以走出地图边界或者走到障碍物上)。此外, 当小 B 扮演的背叛者走到一个传送门上时, 他可以花费 1 单位的时间从当前格子传送到与当前格子相同字母的另一个传送门处(他也可以选择不传送, 此时没有花费任何时间, 待在原地不动)。
传送是双向的。 比如, 现在小 B 走到了标记为 ‘a’ 的格子上, 那么他可以选择花费一单位的时间传送到另一个标记为 ‘a’ 的格子上, 也可以选择不传送, 那么他就待在原地不动。
现在, 小 I 被小 B 的陷阱困住了, 无法移动。 给出地图上小 B 和小 I 所在的格子(他们都站在空地上), 求小 B 最少需要花费多少时间才能走到小 I 所在的格子抓住他。 如果小 I 无法抓住小 B, 输出 -1
时间限制: 1000
内存限制: 65536
输入
第一行一个数字 T, 表示数据组数。 接下来描述 T 组数据, 每组数据最开始是两个正整数 N, M 表示地图是 N 行 M 列的矩形。 接下来 N 行, 每行 M 个字符, 表示地图。 在地图上, 用 ‘.’ 表示空地,‘#’ 表示障碍物, ‘a’-‘z’ 表示传送门, ‘B’ 表示小 B 的初始位置, ‘I’ 表示小 I 的初始位置。 对于每组数据, 保证在地图上标记相同的传送
门恰好出现两次。 T,N,M <= 100
输出
T 行, 第 i 行输出 ‘Case #i: t’, 表示第 i 组数据的答案是 t. 小 B 最少需要 t 单位时间才能走到小 I 所在的格子。 如果小 I 无法抓住小B, 输出 -1
样例输入
3
5 5
Bx#…
#a.#.
…
##…#
.x.aI
5 5
BIa.a
x#.x.
.#.##
…
#####
2 2
B#
#I
样例输出
Case #1: 4
Case #2: 1
Case #3: -1
提示
对于第一组数据, 假设行从上到下标号 1 到 5, 列从左到右标号 1到 5, 小 B 初始在 (1, 1)。 小 B 的最优路线是: (1, 1) -> (1, 2) -> (2,2) -> (5, 4) -> (5, 5)。 也就是走到标记为 x 的传送门时忽略传送门, 走到标记为 a 的传送门时使用传送门。 对于第二组数据, 小 B 直接花费 1 单位时间向右走一格就可以抓住小 I, 故输出 1。 对于第三组数据, 小 B 无法走到小 I 所在的位置上, 故输出 -1。
根据题目描述,我们需要求解小 B 最少需要花费多少时间才能走到小 I 所在的格子抓住他。这是一个寻路问题,可以使用搜索算法来解决。
考虑使用广度优先搜索(BFS)算法来搜索从小 B 到小 I 的最短路径。我们可以从小 B 的初始位置开始,逐步向相邻的格子进行搜索,直到找到小 I。在搜索过程中,需要记录每个格子的位置、步数和是否已经访问过。
具体的算法步骤如下:
-
创建一个队列,并将小 B 的初始位置加入队列。
-
创建一个二维数组
visited
来记录每个格子是否已经访问过,初始时所有元素设为false
。 -
创建一个二维数组
steps
来记录从小 B 到达每个格子的步数,初始时所有元素设为 0。 -
使用一个循环来遍历队列中的元素,直到队列为空:
-
从队列中取出一个格子的位置
(x, y)
。 -
如果该格子是小 I 所在的格子,返回步数
steps[x][y]
。 -
否则,将该格子的位置标记为已访问,并将其相邻的空地格子加入队列中。
-
如果相邻格子是传送门,可以选择传送或不传送,分别将传送和不传送的格子加入队列。
-
如果相邻格子是空地,将其加入队列,并将步数增加 1。
-
- 如果队列为空仍未找到小 I,则返回 -1。
根据上述算法,我们可以编写如下的 C 代码来解决这个问题:
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#define MAX_N 100
#define MAX_M 100
#define MAX_QUEUE_SIZE (MAX_N * MAX_M)
typedef struct {
int x;
int y;
} Point;
typedef struct {
Point points[MAX_QUEUE_SIZE];
int front;
int rear;
} Queue;
void initQueue(Queue *queue) {
queue->front = 0;
queue->rear = 0;
}
bool isQueueEmpty(Queue *queue) {
return queue->front == queue->rear;
}
void enqueue(Queue *queue, Point point) {
queue->points[queue->rear++] = point;
}
Point dequeue(Queue *queue) {
return queue->points[queue->front++];
}
bool isValid(int x, int y, int n, int m) {
return x >= 0 && x < n && y >= 0 && y < m;
}
int minTimeToCatchB(char map[MAX_N][MAX_M], int n, int m) {
Point start;
Point target;
// Find the positions of B and I
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (map[i][j] == 'B') {
start.x = i;
start.y = j;
} else if (map[i][j] == 'I') {
target.x = i;
target.y = j;
}
}
}
bool visited[MAX_N][MAX_M];
memset(visited, false, sizeof(visited));
int steps[MAX_N][MAX_M];
memset(steps, 0, sizeof(steps));
Queue queue;
initQueue(&queue);
enqueue(&queue, start);
visited[start.x][start.y] = true;
while (!isQueueEmpty(&queue)) {
Point curr = dequeue(&queue);
if (curr.x == target.x && curr.y == target.y) {
return steps[curr.x][curr.y];
}
// Check adjacent cells
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
for (int i = 0; i < 4; i++) {
int nx = curr.x + dx[i];
int ny = curr.y + dy[i];
if (isValid(nx, ny, n, m) && !visited[nx][ny]) {
char cell = map[nx][ny];
if (cell == '#' || (cell >= 'a' && cell <= 'z' && map[curr.x][curr.y] == cell)) {
// Skip obstacles and same teleporter
continue;
}
visited[nx][ny] = true;
enqueue(&queue, (Point){nx, ny});
steps[nx][ny] = steps[curr.x][curr.y] + 1;
}
}
}
return -1; // Target not reachable
}
int main() {
int T;
scanf("%d", &T);
for (int caseNum = 1; caseNum <= T; caseNum++) {
int n, m;
scanf("%d %d", &n, &m);
char map[MAX_N][MAX_M];
for (int i = 0; i < n; i++) {
scanf("%s", map[i]);
}
int minTime = minTimeToCatchB(map, n, m);
printf("Case #%d: %d\n", caseNum, minTime);
}
return 0;
}
此代码使用了一个队列来实现广度优先搜索,通过循环遍历队列中的元素来搜索路径。在搜索过程中,使用一个二维数组 visited
来记录已访问的格子,使用另一个二维数组 steps
来记录从初始位置到达每个格子的步数。如果找到了小 I 的位置,就返回对应的步数;如果队列为空,仍未找到小 I,就返回 -1。