P4924 [1007] 魔法少女小Scarlet
P4924 [1007] 魔法少女小Scarlet
题目描述
Scarlet 最近学会了一个数组魔法,她会在 n × n n\times n n×n 二维数组上将一个奇数阶方阵按照顺时针或者逆时针旋转 9 0 ∘ 90^\circ 90∘。
首先,Scarlet 会把 1 1 1 到 n 2 n^2 n2 的正整数按照从左往右,从上至下的顺序填入初始的二维数组中,然后她会施放一些简易的魔法。
Scarlet 既不会什么分块特技,也不会什么 Splay 套 Splay,她现在提供给你她的魔法执行顺序,想让你来告诉她魔法按次执行完毕后的二维数组。
输入格式
第一行两个整数 n , m n,m n,m,表示方阵大小和魔法施放次数。
接下来 m m m 行,每行 4 4 4 个整数 x , y , r , z x,y,r,z x,y,r,z,表示在这次魔法中,Scarlet 会把以第 x x x 行第 y y y 列为中心的 2 r + 1 2r+1 2r+1 阶矩阵按照某种时针方向旋转,其中 z = 0 z=0 z=0 表示顺时针, z = 1 z=1 z=1 表示逆时针。
输出格式
输出 n n n 行,每行 n n n 个用空格隔开的数,表示最终所得的矩阵
输入输出样例 #1
输入 #1
5 4
2 2 1 0
3 3 1 1
4 4 1 0
3 3 2 1
输出 #1
5 10 3 18 15
4 19 8 17 20
1 14 23 24 25
6 9 2 7 22
11 12 13 16 21
说明/提示
对于50%的数据,满足 r = 1 r=1 r=1
对于100%的数据 1 ≤ n , m ≤ 500 1\leq n,m\leq500 1≤n,m≤500,满足 1 ≤ x − r ≤ x + r ≤ n , 1 ≤ y − r ≤ y + r ≤ n 1\leq x-r\leq x+r\leq n,1\leq y-r\leq y+r\leq n 1≤x−r≤x+r≤n,1≤y−r≤y+r≤n。
实现历程
普通的旋转方法
// 1 2 3 4 5
// 6 7 8 9 10
// 11 12 13 14 15
// 16 17 18 19 20
// 21 22 23 24 25
// 11 6 1 4 5
// 12 7 2 9 10
// 13 8 3 14 15
// 16 17 18 19 20
// 21 22 23 24 25
// 11 6 1 4 5
// 12 9 14 19 10
// 13 2 3 18 15
// 16 7 8 17 20
// 21 22 23 24 25
// 11 6 1 4 5
// 12 9 14 19 10
// 13 2 23 8 3
// 16 7 24 17 18
// 21 22 25 20 15
// 11 6 1 4 5
// 12 9 14 19 10
// 13 2 23 8 3
// 16 7 24 17 18
// 21 22 25 20 15
// 5 10 3 18 15
// 4 19 8 17 20
// 1 14 23 24 25
// 6 9 2 7 22
// 11 12 13 16 21
// 顺时针旋转90度
// 1 2 3
// 6 7 8
// 11 12 13
// 先上下翻转
// 11 12 13
// 6 7 8
// 1 2 3
// 然后再按主对角线翻转
// 11 6 1
// 12 7 2
// 13 8 3
// 逆时针翻转90度
// 7 2 9
// 8 3 14
// 17 18 19
// 先左右翻转
// 9 2 7
// 14 3 8
// 19 18 17
// 然后再按主对角线翻转
// 9 14 19
// 2 3 18
// 7 8 17
10分的做法
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int m = scan.nextInt();
int a[][] = new int[n][n];
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
a[i][j] = i*n + j + 1;
}
}
for(int k = 0; k < m; k++) {
int x = scan.nextInt()-1;
int y = scan.nextInt()-1;
int r = scan.nextInt();
int z = scan.nextInt();
if (x - r < 0 || x + r >= n || y - r < 0 || y + r >= n) {
continue; // 如果输入数据不合法,跳过本次操作
}
if(z == 0) {
for(int i = x-r; i < x; i++) {
for(int j = y-r; j <= y+r; j++) {
int t = a[i][j];
a[i][j] = a[2*x-i][j];
a[2*x-i][j] = t;
}
}
for(int i = x-r; i <= x+r; i++) {
for(int j = i+1; j <= y+r; j++) {
int t = a[i][j];
a[i][j] = a[j-(y-x)][i+(y-x)];
a[j-(y-x)][i+(y-x)] = t;
}
}
} else {
for(int i = x-r; i <= x+r; i++) {
for(int j = y-r; j < y; j++) {
int t = a[i][j];
a[i][j] = a[i][2*y-j];
a[i][2*y-j] = t;
}
}
for(int i = x-r; i <= x+r; i++) {
for(int j = i+1; j <= y+r; j++) {
int t = a[i][j];
a[i][j] = a[j-(y-x)][i+(y-x)];
a[j-(y-x)][i+(y-x)] = t;
}
}
}
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
System.out.print(a[i][j]+" ");
}
System.out.println();
}
scan.close();
}
}
90分的做法
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取方阵大小 n 和魔法施放次数 m
int n = scanner.nextInt();
int m = scanner.nextInt();
// 初始化二维数组,将 1 到 n^2 的正整数按从左往右、从上至下的顺序填入
int[][] matrix = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
matrix[i][j] = i * n + j + 1;
}
}
// 执行 m 次魔法操作
for (int k = 0; k < m; k++) {
int x = scanner.nextInt() - 1;
int y = scanner.nextInt() - 1;
int r = scanner.nextInt();
int z = scanner.nextInt();
// 提取以 (x, y) 为中心的 2r + 1 阶子矩阵
int side = 2 * r + 1;
int[][] subMatrix = new int[side][side];
for (int i = 0; i < side; i++) {
for (int j = 0; j < side; j++) {
subMatrix[i][j] = matrix[x - r + i][y - r + j];
}
}
// 根据 z 的值进行顺时针或逆时针旋转
if (z == 0) {
subMatrix = rotateClockwise(subMatrix);
} else {
subMatrix = rotateCounterClockwise(subMatrix);
}
// 将旋转后的子矩阵放回原矩阵
for (int i = 0; i < side; i++) {
for (int j = 0; j < side; j++) {
matrix[x - r + i][y - r + j] = subMatrix[i][j];
}
}
}
// 输出最终的矩阵
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.print(matrix[i][j]);
if (j < n - 1) {
System.out.print(" ");
}
}
System.out.println();
}
scanner.close();
}
// 顺时针旋转 90 度的方法
public static int[][] rotateClockwise(int[][] matrix) {
int n = matrix.length;
int[][] rotated = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
rotated[j][n - 1 - i] = matrix[i][j];
}
}
return rotated;
}
// 逆时针旋转 90 度的方法
public static int[][] rotateCounterClockwise(int[][] matrix) {
int n = matrix.length;
int[][] rotated = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
rotated[n - 1 - j][i] = matrix[i][j];
}
}
return rotated;
}
}
分层旋转
整体思路
对于一个方阵,我们可以把它看作是由多个同心的 “环”(层)组成的。例如,一个 5x5 的矩阵就有 3 层(从外到内),我们可以逐层对矩阵进行旋转操作。对于每一层,我们只需要将该层四条边上的元素按照顺时针或逆时针方向进行交换即可。
顺时针旋转
分层处理
以边长为 side
的方阵为例,我们从最外层(layer = 0
)开始,依次向里处理每一层,直到处理到最内层(layer = side / 2 - 1
)。对于每一层,我们需要确定该层四条边的起始和结束位置。
确定边界
对于第 layer
层,first
表示该层四条边的起始索引,last
表示结束索引。first = layer
,last = side - 1 - layer
。
元素交换
在每一层中,我们从 first
开始,到 last - 1
结束,依次处理该层四条边上的元素。对于每一个元素,我们通过临时变量 temp
来进行元素交换,具体步骤如下:
- 保存当前 “上” 边元素的值到
temp
。 - 将 “左” 边元素的值赋给 “上” 边对应位置。
- 将 “下” 边元素的值赋给 “左” 边对应位置。
- 将 “右” 边元素的值赋给 “下” 边对应位置。
- 将
temp
(原来 “上” 边元素的值)赋给 “右” 边对应位置。
Java 代码示例
if (z == 0) {
// 顺时针旋转
//这是一个条件判断语句,`z` 表示旋转方向,当 `z` 的值为 `0` 时,意味着要进行顺时针旋转,所以执行大括号内的旋转操作代码。
for (int layer = 0; layer < side / 2; layer++) {
//这是一个外层 `for` 循环,用于遍历矩阵的每一层。`layer` 是当前处理的层数,从最外层(`layer = 0`)开始。`side` 是要旋转的子矩阵的边长,`side / 2` 是因为一个方阵最多有 `side / 2` 层需要旋转(例如 3x3 矩阵有 1 层需要旋转,5x5 矩阵有 2 层需要旋转)。
int first = layer;
//`first` 表示当前层四条边的起始索引。每一层的起始索引是从当前层数 `layer` 开始的,例如最外层 `layer = 0` 时,起始索引就是 `0`。
int last = side - 1 - layer;
//`last` 表示当前层四条边的结束索引。它是通过 `side - 1 - layer` 计算得到的,这样可以确保在处理每一层时,正确地定位到该层四条边的边界。例如对于 3x3 矩阵的最外层(`layer = 0`),`last = 2`。
for (int i = first; i < last; i++) {
//这是一个内层 `for` 循环,用于遍历当前层四条边上的元素。从当前层的起始索引 `first` 开始,到结束索引 `last - 1` 结束。因为四条边的元素是相互关联的,我们只需要处理到倒数第二个元素即可完成旋转。
int offset = i - first;
//`offset` 表示当前元素相对于起始索引 `first` 的偏移量。在后续的元素交换过程中,这个偏移量可以帮助我们准确地定位到四条边上对应位置的元素。
int temp = matrix[x - r + first][y - r + i];
//这里创建了一个临时变量 `temp`,用于保存当前 “上” 边元素的值。`x` 和 `y` 是子矩阵的中心坐标,`r` 是子矩阵的半径。`matrix[x - r + first][y - r + i]` 表示当前层 “上” 边的元素。
// 左到上
matrix[x - r + first][y - r + i] = matrix[x - r + last - offset][y - r + first];
//这行代码实现了 “左到上” 的元素交换。将当前层 “左” 边对应位置的元素值赋给 “上” 边的元素。`matrix[x - r + last - offset][y - r + first]` 表示 “左” 边的元素。
// 下到左
matrix[x - r + last - offset][y - r + first] = matrix[x - r + last][y - r + last - offset];
//这行代码实现了 “下到左” 的元素交换。将当前层 “下” 边对应位置的元素值赋给 “左” 边的元素。`matrix[x - r + last][y - r + last - offset]` 表示 “下” 边的元素。
// 右到下
matrix[x - r + last][y - r + last - offset] = matrix[x - r + i][y - r + last];
//这行代码实现了 “右到下” 的元素交换。将当前层 “右” 边对应位置的元素值赋给 “下” 边的元素。`matrix[x - r + i][y - r + last]` 表示 “右” 边的元素。
// 上到右
matrix[x - r + i][y - r + last] = temp;
//这行代码实现了 “上到右” 的元素交换。将之前保存到 `temp` 中的 “上” 边元素的值赋给 “右” 边的元素,完成了一次元素的顺时针旋转。
}
//内层 `for` 循环结束,意味着当前层的所有元素都已经完成了顺时针旋转。
}
//外层 `for` 循环结束,意味着所有层都已经完成了顺时针旋转,整个子矩阵的顺时针旋转操作完成。
}
逆时针旋转的代码逻辑与顺时针旋转类似,只是元素交换的顺序不同,通过改变赋值的顺序实现逆时针的旋转效果。
逆时针旋转
逆时针旋转的思路与顺时针旋转类似,只是元素交换的顺序不同。具体步骤如下:
- 保存当前 “上” 边元素的值到
temp
。 - 将 “右” 边元素的值赋给 “上” 边对应位置。
- 将 “下” 边元素的值赋给 “右” 边对应位置。
- 将 “左” 边元素的值赋给 “下” 边对应位置。
- 将
temp
(原来 “上” 边元素的值)赋给 “左” 边对应位置。
Java 代码示例
else {
// 逆时针旋转
for (int layer = 0; layer < side / 2; layer++) {
int first = layer;
int last = side - 1 - layer;
for (int i = first; i < last; i++) {
int offset = i - first;
int temp = matrix[x - r + first][y - r + i];
// 右到上
matrix[x - r + first][y - r + i] = matrix[x - r + i][y - r + last];
// 下到右
matrix[x - r + i][y - r + last] = matrix[x - r + last][y - r + last - offset];
// 左到下
matrix[x - r + last][y - r + last - offset] = matrix[x - r + last - offset][y - r + first];
// 上到左
matrix[x - r + last - offset][y - r + first] = temp;
}
}
}
示例解释
假设我们有一个 3x3 的矩阵,要对其进行顺时针旋转:
1 2 3
4 5 6
7 8 9
对于外层(layer = 0
):
first = 0
,last = 2
- 当i = 0时:
temp = 1
- “左到上”:
matrix[0][0] = 7
- “下到左”:
matrix[2][0] = 9
- “右到下”:
matrix[2][2] = 3
- “上到右”:
matrix[0][2] = 1
- 当 i = 1时:
temp = 2
- “左到上”:
matrix[0][1] = 4
- “下到左”:
matrix[2][1] = 8
- “右到下”:
matrix[2][2] = 6
- “上到右”:
matrix[0][2] = 2
旋转后的矩阵为:
7 4 1
8 5 2
9 6 3
通过这种分层旋转的方式,我们可以高效地对矩阵进行顺时针或逆时针旋转,避免了额外的空间开销。
满分答案
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[][] matrix = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
matrix[i][j] = i * n + j + 1;
}
}
for (int k = 0; k < m; k++) {
int x = scanner.nextInt() - 1;
int y = scanner.nextInt() - 1;
int r = scanner.nextInt();
int z = scanner.nextInt();
int side = 2 * r + 1;
if (z == 0) {
// 顺时针旋转
for (int layer = 0; layer < side / 2; layer++) {
int first = layer;
int last = side - 1 - layer;
for (int i = first; i < last; i++) {
int offset = i - first;
int temp = matrix[x - r + first][y - r + i];
// 左到上
matrix[x - r + first][y - r + i] = matrix[x - r + last - offset][y - r + first];
// 下到左
matrix[x - r + last - offset][y - r + first] = matrix[x - r + last][y - r + last - offset];
// 右到下
matrix[x - r + last][y - r + last - offset] = matrix[x - r + i][y - r + last];
// 上到右
matrix[x - r + i][y - r + last] = temp;
}
}
} else {
// 逆时针旋转
for (int layer = 0; layer < side / 2; layer++) {
int first = layer;
int last = side - 1 - layer;
for (int i = first; i < last; i++) {
int offset = i - first;
int temp = matrix[x - r + first][y - r + i];
// 右到上
matrix[x - r + first][y - r + i] = matrix[x - r + i][y - r + last];
// 下到右
matrix[x - r + i][y - r + last] = matrix[x - r + last][y - r + last - offset];
// 左到下
matrix[x - r + last][y - r + last - offset] = matrix[x - r + last - offset][y - r + first];
// 上到左
matrix[x - r + last - offset][y - r + first] = temp;
}
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.print(matrix[i][j]+" ");
}
System.out.println();
}
scanner.close();
}
}