[백준] 배열 돌리기4

✔️ 문제

  • 문제 링크 : https://www.acmicpc.net/problem/17406
  • 삼성전자 상시 SW 역량테스트 A형 기출 문제다.

✔️ 문제 이해

  • 크기가 NxN인 배열 A가 있다.

  • 배열 A의 값은 각 행에 있는 모든 수의 합 중 최솟값을 의미한다.

  • 배열 A가 아래와 같은 경우 배열 A의 값은 4다.

  • 1 2 3 // 행의 합 6
    2 1 1 // 행의 합 4
    4 5 6 // 행의 합 15
    
  • 배열은 회전 연산을 수행할 수 있다.

  • 회전 연산은 세 정수 (r, c, s)로 이루어져 있고, 가장 왼쪽 윗 칸이 (r-s, c-s), 가장 오른쪽 아랫 칸이 (r+s, c+s)인 정사각형을 시계 방향으로 한 칸씩 돌린다는 의미이다. 배열의 칸 (r, c)는 r행 c열을 의미한다.

  • A[1][1]   A[1][2] → A[1][3] → A[1][4] → A[1][5] → A[1][6]
                 ↑                                       ↓
    A[2][1]   A[2][2]   A[2][3] → A[2][4] → A[2][5]   A[2][6]
                 ↑         ↑                   ↓         ↓
    A[3][1]   A[3][2]   A[3][3]   A[3][4]   A[3][5]   A[3][6]
                 ↑         ↑                   ↓         ↓
    A[4][1]   A[4][2]   A[4][3] ← A[4][4] ← A[4][5]   A[4][6]
                 ↑                                       ↓
    A[5][1]   A[5][2] ← A[5][3] ← A[5][4] ← A[5][5] ← A[5][6]
      
    A[6][1]   A[6][2]   A[6][3]   A[6][4]   A[6][5]   A[6][6]
    
  • 배열 A와 사용 가능한 회전 연산이 주어졌을 때, 배열 A의 최솟값을 구하자.
  • 회전 연산은 모두 한 번씩 사용해야 하며, 순서는 임의로 정해도 된다.

✔️ 알고리즘 설계

  • 주어진 K개의 회전 연산을 어떤 순서로 배열에 적용하는지에 따라 A 배열의 값이 달라진다.
  • K개의 회전 연산에 대해 next_permutation을 사용하여 회전 연산의 모든 순열을 구하고
  • 각 순열마다 배열 A에 회전 연산을 적용하고, 배열의 값을 구해보며 최솟값을 찾는다.

✔️ 상세 구현 설명

  • r, c, s 의 값을 가지는 회전연산을 구조체로 관리했다. 구조체 배열을 만들어 모든 회전 연산을 저장했다.
  • 주어진 회전 연산들의 모든 순열을 구해야하는데, 회전 연산들이 저장된 구조체 배열의 인덱스를 배열로 만들어 next_permutation 을 적용시켰다.
  • 회전 시키는법
    • 주어진 회전 연산 r, c, s 값을 활용한다.
    • r, c, s 값을 활용하여 기준점 sx, sy를 잡는다. (아래 빨간색 박스가 좌표가 (sx,sy)인 기준점이다.)
    • 이제 이 기준점을 배열 중앙으로, 대각선 방향으로 한 칸씩 옮길 것이다. (빨, 주, 노, 초 순서로)
    • 기준점을 옮길때마다 테두리 부분을 회전 시킨다. (아래 그림 참조)
    • 주어진 모든 회전 연산들에 대해서 위 과정을 수행한다.
    • 테두리 부분회전은 원본 배열을 복사해둔 임시 배열 tmp를 하나 만들어두고, 임시 배열의 값을 참조하면서 원본 배열(테두리 회전 결과를 저장할 배열) tmpMap에 시계방향으로 한 칸씩 옆으로 원소를 옮기면서 넣어주면 된다.

👨🏻‍💻 소스 코드

#include<iostream>
#include<vector>
#include<algorithm>
#include<unordered_map>
#include<cstring>
using namespace std;

typedef struct {
	int r, c, s;
}rotationInfo;

int n, m, k;
int map[50][50];
int tmpMap[50][50];
unordered_map<int, rotationInfo> info;
vector<int> arr;

void rotate(rotationInfo info) {
	int r = info.r;
	int c = info.c;
	int s = info.s;
	int tmp[50][50];
	memcpy(tmp, tmpMap, sizeof(tmpMap));

	int len = (r + s) - (r - s) + 1;
	int cnt = len / 2 + 1;
	int sx = r - s - 1, sy = c - s -1;

	while (cnt--) {
		// 위쪽 행  →
		for (int col = sy; col < sy + len - 1; col++) {
			tmpMap[sx][col + 1] = tmp[sx][col];
		}

		// 오른쪽 열 ↓
		for (int row = sx; row < sx + len - 1; row++) {
			tmpMap[row + 1][sy + len - 1] = tmp[row][sy + len - 1];
		}
	
		// 아래쪽 행  ←
		for (int col = sy + len - 1; col > sy; col--) {
			tmpMap[sx + len - 1][col-1] = tmp[sx+len - 1][col];
		}

		// 왼쪽 열 ↑
		for (int row = sx + len - 1; row > sx; row--) {
			tmpMap[row - 1][sy] = tmp[row][sy];
		}
		sx++, sy++, len-=2;
	}
}

int main() {
	int ret = 0x7fffffff;

	cin >> n >> m >> k;

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			cin >> map[i][j];
		}
	}

	for (int i = 0; i < k; i++) {
		int r, c, s;
		cin >> r >> c >> s;
		info[i] = { r,c,s };
		arr.push_back(i);
	}

	do {
		memcpy(tmpMap, map, sizeof(map));
	
		for (int order : arr) {
			rotate(info[order]);
		}

		for (int r = 0; r < n; r++) {
			int candi = 0;
			for (int c = 0; c < m; c++) {
				candi += tmpMap[r][c];
			}
			ret = min(ret, candi);
		}
	} while (next_permutation(arr.begin(), arr.end()));

	cout << ret;

	return 0;
}

✔️ 문제 회고

  • 순열을 활용하는 브루트 포스 문제다.
  • 2차원 배열 조작은 임시 배열을 사용하는 것이 편리하다.
  • 2차원 배열 회전, 대칭을 구현할 수 있어야 한다. 그림을 그려가며 하나씩 해보면 어렵지 않다.