컴굥일지

[BOJ/백준 17406][C++] 배열 돌리기 4 본문

알고리즘/코테 문제

[BOJ/백준 17406][C++] 배열 돌리기 4

gyong 2023. 8. 14. 18:59
반응형

문제

https://www.acmicpc.net/problem/17406

백준 17406
백준 17406

백준 17406

 

문제 내용

크기가 N*M인 배열 A가 있다.

배열 A의 값은 각 행별로 합을 구한 후, 그 중에서 최솟값을 의미한다.

배열은 회전 연산을 수행할 수 있다. 회전 연산 (r, c, s)가 주어지면 (r-s, c-s) 부터 (r+s, c+s) 까지의 정사각형을 (r,c)를 중심으로 시계방향으로 한 칸 회전시키는 것이다. 

 

N*M 크기의 배열과 회전 연산 k개를 입력 받아, 연산 k개의 순서를 조정하여 배열의 값이 최소가 되는 값을 구하면 된다. (단, 연산 k개는 모두 1번 씩 수행해야 한다.) 

 

문제 풀이

이 문제는 구현할 양이 꽤 많다. 그렇기 때문에 과정을 나누어 해결할 필요가 있다.

1. 배열의 값 구하기 => 단순 구현

2. 연산 k개의 순서 정하기 => 백트래킹 사용

3. 회전 연산 구현하기 => 인덱스를 주의하여 구현

 

1. 배열의 값 구하기

rotatingArr은 전역으로 선언 해두었다.

calcRow()는 각 행별로 원소의 합을 구하여 반환하는 함수이다.

calcArr()은 반복문을 돌며 calcRow로 각 행의 값을 구한 후, 매번 min값을 업데이트 하여 배열의 값을 반환한다. 

int calcRow(int r) {
    int ssum = 0;
    for (int i = 1; i <= m; i++) {
        ssum += rotatingArr[r][i];
    }
    return ssum;
}

int calcArr() {
    int mmin = 1e5;
    for (int i = 1; i <= n; i++) {
        mmin = min(mmin, calcRow(i));
    }
    return mmin;
}

 

2. 연산 k개의 순서 구하기

백트래킹으로 k개의 순서를 구할 수 있다. k가 최대 6이기 때문에 백트래킹으로 풀어도 된다.

종료조건은 cnt==k일 때로 설정하고, k개 연산의 순서는 orderResult에 담는다.

종료 조건을 만족했을 때 orderResult에 저장해둔 순서대로 연산을 수행하고 결과를 업데이트 하면 된다.

int orderResult[6];
bool used[6];

void decideOrder(int cnt) {
    if (cnt == k) {
        // 순서대로 연산을 수행하고 결과 업데이트
        copy(arr.begin(), arr.end(), rotatingArr.begin());
        rotateArr();

        int num = calcArr();
        minimumResult = min(minimumResult, num);
        return;
    }

    for (int i = 0; i < k; i++) {
        if (!used[i]) {
            used[i] = true;
            orderResult[cnt] = i;
            decideOrder(cnt + 1);
            used[i] = false;
        }
    }
}

 

3. 회전 연산 구현하기

이 부분이 제일 까다롭다.

회전을 시키려는데, 바로 맨처음에 입력받은 arr에서 작업을하면, 다른 순서로 연산했을 때 기준으로 할 값이 사라지므로, arr을 rotatingArr로 복사를 해둬야 한다.  이 과정은 백트래킹 cnt==k일 때 수행해둔다.

이후 rotateArr()를 호출하는데, rotateArr()은 연산의 r, c, s 값을 얻어 계산을 수행하는 rotate()를 호출한다.

 

rotate()에서 회전을 수행하는데, 회전 방법을 살펴보자.

(r-s, c-s) 부터 (r+s, c+s) 까지의 정사각형을 회전시키면 (r,c)를 중심으로 회전하게 된다. 또한, (r,c)부터의 거리가 같은 칸끼리 회전이 된다. (레이어 별로 계산이 된다.)

 

레이어 별로 계산을 하기 위한 함수가 rotateBorder()이다.

정사각형의 각 꼭지점에 해당하는 부분은 회전을 시키다가 값이 덮어써질 위험이 있으므로, 미리 값을 LT, RT, LB, RB 변수에 따로 저장해두었다. 

이후 for문을 돌며 각 변의 원소를 회전시키면 된다. (단, 방향에 주의해야 한다.)

마지막으로 각 꼭지점에 LT, RT, LB, RB를 넣으면 된다.

void rotateBorder(int r, int c, int s) {
    int LT = rotatingArr[r - s + 1][c - s];
    int RT = rotatingArr[r - s][c + s - 1];
    int LB = rotatingArr[r + s][c - s + 1];
    int RB = rotatingArr[r + s - 1][c + s];

    for (int i = c + s - 1; i > c - s; i--) {
        rotatingArr[r - s][i] = rotatingArr[r - s][i - 1];
    }
    for (int i = c - s + 1; i < c + s; i++) {
        rotatingArr[r + s][i] = rotatingArr[r + s][i + 1];
    }
    for (int i = r - s + 1; i < r + s; i++) {
        rotatingArr[i][c - s] = rotatingArr[i + 1][c - s];
    }
    for (int i = r + s - 1; i > r - s; i--) {
        rotatingArr[i][c + s] = rotatingArr[i - 1][c + s];
    }
    rotatingArr[r - s][c - s] = LT;
    rotatingArr[r - s][c + s] = RT;
    rotatingArr[r + s][c - s] = LB;
    rotatingArr[r + s][c + s] = RB;
}

void rotate(int r, int c, int s) {
    for (int i = 1; i <= s; i++) {
        rotateBorder(r, c, i);
    }
}

void rotateArr() {
    for (int i = 0; i < k; i++) {
        int order = orderResult[i];
        int r = kArr[order][0], c = kArr[order][1], s = kArr[order][2];

        rotate(r, c, s);
    }
}

 

입출력을 포함환 1~3 과정 전체에 해당하는 코드는 아래와 같다.

 

전체 코드

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

int n, m, k;
vector<vector<int>> arr;
vector<vector<int>> rotatingArr;

vector<vector<int>> kArr;

int orderResult[6];
bool used[6];

int minimumResult = 1e5;

int calcRow(int r) {
    int ssum = 0;
    for (int i = 1; i <= m; i++) {
        ssum += rotatingArr[r][i];
    }
    return ssum;
}

int calcArr() {
    int mmin = 1e5;
    for (int i = 1; i <= n; i++) {
        mmin = min(mmin, calcRow(i));
    }
    return mmin;
}

void rotateBorder(int r, int c, int s) {
    int LT = rotatingArr[r - s + 1][c - s];
    int RT = rotatingArr[r - s][c + s - 1];
    int LB = rotatingArr[r + s][c - s + 1];
    int RB = rotatingArr[r + s - 1][c + s];

    for (int i = c + s - 1; i > c - s; i--) {
        rotatingArr[r - s][i] = rotatingArr[r - s][i - 1];
    }
    for (int i = c - s + 1; i < c + s; i++) {
        rotatingArr[r + s][i] = rotatingArr[r + s][i + 1];
    }
    for (int i = r - s + 1; i < r + s; i++) {
        rotatingArr[i][c - s] = rotatingArr[i + 1][c - s];
    }
    for (int i = r + s - 1; i > r - s; i--) {
        rotatingArr[i][c + s] = rotatingArr[i - 1][c + s];
    }
    rotatingArr[r - s][c - s] = LT;
    rotatingArr[r - s][c + s] = RT;
    rotatingArr[r + s][c - s] = LB;
    rotatingArr[r + s][c + s] = RB;
}

void rotate(int r, int c, int s) {
    for (int i = 1; i <= s; i++) {
        rotateBorder(r, c, i);
    }
}

void rotateArr() {
    for (int i = 0; i < k; i++) {
        int order = orderResult[i];
        int r = kArr[order][0], c = kArr[order][1], s = kArr[order][2];

        rotate(r, c, s);
    }
}

void decideOrder(int cnt) {
    if (cnt == k) {
        // 순서대로 연산을 수행하고 결과 업데이트
        copy(arr.begin(), arr.end(), rotatingArr.begin());
        rotateArr();

        int num = calcArr();
        minimumResult = min(minimumResult, num);
        return;
    }

    for (int i = 0; i < k; i++) {
        if (!used[i]) {
            used[i] = true;
            orderResult[cnt] = i;
            decideOrder(cnt + 1);
            used[i] = false;
        }
    }
}

int main() {
    cin >> n >> m >> k;

    arr.assign(n + 1, vector<int>(m + 1, 0));
    rotatingArr.assign(n + 1, vector<int>(m + 1));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> arr[i][j];
        }
    }

    int r, c, s;
    for (int i = 0; i < k; i++) {
        cin >> r >> c >> s;
        vector<int> tmp = {r, c, s};
        kArr.push_back(tmp);
    }

    decideOrder(0);
    cout << minimumResult;
}

 

반응형
Comments