문제

  • 문제 링크
  • 문제 설명: 현재 카드가 놓인 상태를 나타내는 2차원 배열 board와 커서의 처음 위치 r, c가 매개변수로 주어질 때, 모든 카드를 제거하기 위한 키 조작 횟수의 최솟값을 return 하도록 solution 함수를 완성해 주세요.
    • 유저가 카드를 2장 선택하여 앞면으로 뒤집었을 때 같은 그림이 그려진 카드면 해당 카드는 게임 화면에서 사라지며, 같은 그림이 아니라면 원래 상태로 뒷면이 보이도록 뒤집음. 이와 같은 방법으로 모든 카드를 화면에서 사라지게 하면 게임이 종료
    • 커서 키 조작은 상하좌우로 1칸씩 이동가능하며, 상하좌우에서 가장 가까운 카드로 이동하거나 없으면 마지막 칸으로 이동하는 점프 조작도 있다.
    • 1칸 이동, 점프 이동, 카드 뒤집기 모두 1회 조작한 것으로 본다.
  • 입력
    • board는 4 x 4 크기의 2차원 배열
    • board 배열의 각 원소는 0 이상 6 이하인 자연수 (0은 빈칸)
    • 1 부터 6까지의 자연수는 2개씩 들어있으며 같은 숫자는 같은 그림의 카드를 의미
    • 뒤집을 카드가 없는 경우(board의 모든 원소가 0인 경우)는 입력으로 주어지지 않음
    • r은 커서의 최초 세로(행) 위치, c는 커서의 최초 가로(열) 위치
    • 게임 화면의 좌측 상단이 (0, 0), 우측 하단이 (3, 3)
  • 출력: 최소 키 조작 횟수
board r c result
[[1,0,0,3],[2,0,0,0],[0,0,0,2],[3,0,1,0]] 1 0 14
[[3,0,0,2],[0,0,1,0],[0,1,0,0],[2,0,0,3]] 0 1 16

풀이

  • 서로 다른 카드를 뒤집게 되는 경우는 없다고 가정
  • 카드 쌍의 각 좌표를 배열에 저장
  • 카드의 종류가 최대 6가지이므로, 매 카드마다 제거하는 순서를 조합해서 최소 비용을 탐색
    • 카드를 제거할 때 순서에 영향을 받으므로 "1번 카드 쌍 뒤집기 -> 2번 카드 쌍 뒤집기" 와 "2번 카드 쌍 뒤집기 -> 1번 카드 쌍 뒤집기" 의 경우는 다른 케이스로 생각해야 함
    • 카드가 이미 제거되었는지는 정수형 변수에 1-bit를 저장해서 판단
    • 제거되지 않은 카드 중에서 하나씩 제거하면서 비용을 계산하는데 두 카드 사이의 거리를 계산하는 함수(cost)를 DFS로 구현
  • cost(pii& src, pii& dest, int visited) := src에서 dest로 이동할 때 최소 조작 횟수 반환
    • visited는 현재 좌표의 방문 유무이며 4x4 배열이기 때문에 칸의 개수가 총 16개라 (1 << (y*4+x)) 번째 비트를 on/off 하여 파악할 수 있음
    • 카드 뒤집기 연산은 포함하지 않고 이동만 포함
  • dfs(pii cursor, int flipped, vector<pair<pii, pii>> loc) := 현재 커서의 위치 cursor와 현재 뒤집힌 카드 목록 flipped 와 각 카드의 좌표가 담긴 배열 loc 주어질 때 모두 뒤집을 때까지 필요한 최소 비용 반환
  • 주의할 점: cost(src, dest, 0) 와 cost(dest, src, 0) 의 결과는 다를 수 있음
    • 예를 들어, 아래와 같은 경우 라이언 카드는 위 함수의 호출 결과 각각 3과 2가 됨.

  • 정답 코드
#include <string>
#include <vector>
#include <algorithm>
#define Y first
#define X second
using namespace std;
typedef pair<int,int> pii;

const int INF = 1e9;
const int d[5] = {0, -1, 0, 1, 0};
vector<vector<int>> B;

int cost(pii src, pii dest, int visited) {
    if (src.Y == dest.Y and src.X == dest.X) return 0;
    int ret = INF;
    for (int i = 0; i < 4; i++) {
        int ny = src.Y + d[i];
        int nx = src.X + d[i+1];
        if (ny < 0 or ny >= 4 or nx < 0 or nx >= 4 or (visited & (1 << (ny*4 + nx))))
            continue;
        // 1칸씩 이동
        int nextVisited = visited | (1 << (src.Y*4 + src.X));
        ret = min(ret, cost({ny, nx}, dest, nextVisited) + 1);
        // 점프 이동
        if (ny == src.Y) {
            while (nx > 0 and nx < 3 and B[ny][nx] == 0) {
                nextVisited |= (1 << (ny*4 + nx));
                nx += d[i+1];
            }
            if (nx == src.X + d[i+1]) continue; // 1칸 이동한 것과 같으면 스킵
        } else {
            while (ny > 0 and ny < 3 and B[ny][nx] == 0) {
                nextVisited |= (1 << (ny*4 + nx));
                ny += d[i];
            }
            if (ny == src.Y + d[i]) continue; // 1칸 이동한 것과 같으면 스킵
        }
        ret = min(ret, cost({ny, nx}, dest, nextVisited) + 1);
    }
    return ret;
}

int dfs(pii cursor, int flipped, vector<pair<pii,pii>>& loc) {
    int ret = INF;
    for (int i = 0; i < 6; i++) {
        // 이미 뒤집혀 제거되었거나 존재하지 않는 카드이면 continue
        if ((flipped & (1 << i)) or loc[i].first.Y == -1) continue;
        pii& src = loc[i].first;
        pii& dest = loc[i].second;
        // 현재 커서 위치에서 카드쌍의 각 위치까지 조작 비용을 계산해서 비교
        int cost1 = cost(cursor, src, 0) + cost(src, dest, 0);
        int cost2 = cost(cursor, dest, 0) + cost(dest, src, 0);
        B[src.Y][src.X] = B[dest.Y][dest.X] = 0;
        cost1 += dfs(dest, flipped | (1 << i), loc);
        cost2 += dfs(src, flipped | (1 << i), loc);
        ret = min(ret, min(cost1, cost2) + 2); // 2 = flipping cost
        B[src.Y][src.X] = B[dest.Y][dest.X] = i+1;
    }
    return ret == INF ? 0 : ret;
}

int solution(vector<vector<int>> board, int r, int c) {
    B = board;
    vector<pair<pii,pii>> loc(6, {{-1, -1}, {-1, -1}});
    for (int y = 0; y < 4; y++)
        for (int x = 0; x < 4; x++)
            if (board[y][x] > 0)
                if (loc[board[y][x]-1].first.Y == -1)
                    loc[board[y][x]-1].first = {y, x};
                else
                    loc[board[y][x]-1].second = {y, x};
    return dfs({r, c}, 0, loc);
}

 

문제

  • 문제 링크
  • 문제 설명: 프로그램은 2차원 가상 벽면에 기둥과 보를 이용한 구조물을 설치할 수 있는데, 기둥과 보는 길이가 1인 선분으로 표현되며 다음과 같은 규칙을 가지고 있습니다.
    • 기둥은 바닥 위에 있거나 보의 한쪽 끝 부분 위에 있거나, 또는 다른 기둥 위에 있어야 합니다.
    • 보는 한쪽 끝 부분이 기둥 위에 있거나, 또는 양쪽 끝 부분이 다른 보와 동시에 연결되어 있어야 합니다.
    • 기둥은 좌표 (x, y)에서 (x, y+1) 방향으로 놓여짐
    • 보는 좌표 (x, y)에서 (x+1, y) 방향으로 놓여짐
    • 기둥과 보를 삭제한 후에 남은 기둥과 보들 또한 위 규칙을 만족해야 합니다. 만약, 작업을 수행한 결과가 조건을 만족하지 않는다면 해당 작업은 무시됩니다.

  • 벽면의 크기 n, 기둥과 보를 설치하거나 삭제하는 작업이 순서대로 담긴 2차원 배열 build_frame이 매개변수로 주어질 때, 모든 명령어를 수행한 후 구조물의 상태를 return 하도록 solution 함수를 완성해주세요.
  • 입력
    • n은 5 이상 100 이하인 자연수
    • build_frame의 세로(행) 길이는 1 이상 1,000 이하
    • build_frame의 원소는 [x, y, a, b]형태
      • x, y는 기둥, 보를 설치 또는 삭제할 교차점의 좌표 [가로 좌표, 세로 좌표]
      • a는 설치 또는 삭제할 구조물의 종류 (0은 기둥 1은 보)
      • b는 구조물을 설치할 지, 혹은 삭제할 지 (0은 삭제 1은 설치)
      • 벽면을 벗어나게 기둥, 보를 설치하는 경우는 없습니다.
      • 바닥에 보를 설치 하는 경우는 없습니다.
  • 출력: 배열의 원소는 [가로 좌표, 세로 좌표, 구조물의 종류]로 오름차순된 형태
n build_frame result
5 [[1,0,0,1],[1,1,1,1],[2,1,0,1],[2,2,1,1],[5,0,0,1],[5,1,0,1],[4,2,1,1],[3,2,1,1]] [[1,0,0],[1,1,1],[2,1,0],[2,2,1],[3,2,1],[4,2,1],[5,0,0],[5,1,0]]
5 [[0,0,0,1],[2,0,0,1],[4,0,0,1],[0,1,1,1],[1,1,1,1],[2,1,1,1],[3,1,1,1],[2,0,0,0],[1,1,1,0],[2,2,0,1]] [[0,0,0],[0,1,1],[1,1,1],[2,1,1],[3,1,1],[4,0,0]]

풀이

  • 문제에서 정해진 규칙이 유지되는지 검사하는 함수(check)와 배열에 기둥/보를 설치 및 삭제하는 작업을 하는 함수(build)를 작성
  • 기둥과 보 2가지 구조물만 가지므로 비트 연산으로 해당 위치에 구조물의 존재를 파악
  • 설치할 때는 설치 위치에 대해서만 조건을 검사하면 되지만, 삭제할 때는 삭제 후 주변 구조물에 대해 조건을 만족하는지 검사해야 하므로 check함수를 여러 번 호출해주어야 함
  • 작업별 조건
    • 기둥 설치: 설치할 위치가 바닥/보 또는 왼쪽에 보 또는 아래에 기둥
    • 보 설치: 설치할 위치에 기둥 또는 오른쪽에 기둥 또는 양쪽에 보
    • 기둥 삭제: 삭제후 (x-1,y+1), (x,y+1) 위치에 있는 보/기둥에 대해 위의 조건들을 각각 검사
    • 보 삭제: 삭제후 (x-1,y), (x+1,y)에 있는 보 또는 (x,y), (x+1,y)에 있는 기둥의 조건을 각각 검사
  • 정답 코드
#include <string>
#include <vector>
using namespace std;

vector<vector<int>> structure;
bool check(int y, int x, int build_type) {
    bool ret = true; // 경계에 아무것도 없는 경우 삭제/설치 가능하므로 기본은 true
    // 기둥 설치 또는 삭제 시 해당 위치에 기둥이 있으면
    if ((structure[y][x] & 1) or build_type == 0) {
        // 바닥이거나 아래에 기둥이 있거나, 양 끝에 보가 하나 있으면 true
        if (y == 0 or (y > 0 and (structure[y-1][x] & 1)) or
            (structure[y][x] & 2) or (x > 0 and (structure[y][x-1] & 2)))
            ret = true;
        else ret = false;
    }
    // 보 설치 또는 삭제 시 해당 위치에 보가 있으면
    if ((structure[y][x] & 2) or build_type == 1) {
        // 양 끝에 기둥이 하나 있거나, 양 끝 모두 보가 있으면 true
        if ((structure[y-1][x] & 1) or (structure[y-1][x+1] & 1) or
           (x > 0 and (structure[y][x-1] & 2) and (structure[y][x+1] & 2)))
            ret = true;
        else ret = false;
    }
    return ret;
}

void build(vector<vector<int>>& build_frame) {
    for (auto& v : build_frame) {
        int x = v[0], y = v[1], a = v[2];
        if (v[3]) {
            if (check(y, x, a))
                structure[y][x] |= 1 << a;
        } else {
            structure[y][x] ^= 1 << a;
            if (a == 0) { // 기둥 삭제 후 조건 검사
                if (check(y+1, x, -1))
                    if (x == 0 or (x > 0 and check(y+1, x-1, -1)))
                        continue;
            } else { // 보 삭제 후 조건 검사
                if (check(y, x, -1) and check(y, x+1, -1))
                    if (x == 0 or (x > 0 and check(y, x-1, -1)))
                        continue;
            }
            structure[y][x] |= 1 << a;
        }
    }
}

vector<vector<int>> solution(int n, vector<vector<int>> build_frame) {
    vector<vector<int>> answer;
    structure = vector<vector<int>> (n+2, vector<int> (n+2,0));
    build(build_frame);
    for (int x = 0; x < n+1; x++) {
        for (int y = 0; y < n+1; y++) {
            if (structure[y][x] & 1) answer.push_back({x, y, 0});
            if (structure[y][x] & 2) answer.push_back({x, y, 1});
        }
    }
    return answer;
}

 

문제

  • 문제 링크
  • 문제 설명: A, B 두 사람이 s에서 출발해서 각각의 도착 지점(a, b)까지 택시를 타고 간다고 가정할 때, 최저 예상 택시요금을 계산해서 반환하는 함수를 작성
    • 두 사람이 함께 택시를 탈 경우 비용은 1인분으로 낸다. 만약, 아예 합승을 하지 않고 각자 이동하는 경우의 예상 택시요금이 더 낮다면, 합승을 하지 않아도 된다.
  • 입력
    • fares[i] = [c,d,f] 두 지점 c 와 d 사이의 비용 f
    • 지점갯수 n은 3 이상 200 이하인 자연수
    • 지점 s, a, b는 1 이상 n 이하인 자연수이며, 각기 서로 다른 값
    • 즉, 출발지점, A의 도착지점, B의 도착지점은 서로 겹치지 않습니다.
    • 요금 f는 1 이상 100,000 이하인 자연수

 

 

  • 출력: 최저 택시 요금
n s a b fares result
6 4 6 2 [[4, 1, 10], [3, 5, 24], [5, 6, 2], [3, 1, 41], [5, 1, 24], [4, 6, 50], [2, 4, 66], [2, 3, 22], [1, 6, 25]] 82
7 3 4 1 [[5, 7, 9], [4, 6, 4], [3, 6, 1], [3, 2, 3], [2, 1, 6]] 14
6 4 5 6 [[2,6,6], [6,3,7], [4,6,7], [6,5,11], [2,5,12], [5,3,20], [2,4,8], [4,3,9]] 18

풀이

  • n개의 지점 중 합승 후 a와 b가 따로 갈 때 비용을 가장 적게 내는 지점을 찾아야 함
  • a와 b가 s에서부터 합승해서 도착한 지점을 x 라고 할 때 cost[s][x] + cost[x][a] + cost[x][b] 가 최소가 되는 x를 찾으면 된다.
  • x가 s일 경우 두 사람은 완전히 합승하지 않는 것이고 x가 a나 b일 경우 둘 중 한 명의 도착지점까지의 거리 중에 다른 한 명의 도착지점이 있는 것 (경로가 겹쳐진 것)
  • 결국 모든 지점 쌍에 대해 최소 비용을 계산해야 하는데 n의 최대 크기가 200이기 때문에 플로이드-와샬로 모든 쌍에 대해 비용을 계산할 수 있음
  • 주의할 점: 요금 f는 1 이상 100,000 이하인 자연수이므로 200 * 200 * 100000 = 4 * 1e9 가 되는데 중간 계산 결과가 정수형을 넘을 수 있으므로 자료형으로 long을 써야 함
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

int solution(int n, int s, int a, int b, vector<vector<int>> fares) {
    long cost[n][n];
    const long INF = 1e9;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            cost[i][j] = i == j ? 0 : INF; // 비용 배열 초기화
    
    for (auto& v : fares)
        cost[v[0]-1][v[1]-1] = cost[v[1]-1][v[0]-1] = v[2];
    
    // 플로이드-와샬 알고리즘
    for (int k = 0; k < n; k++) { // 매 거쳐갈(via) 지점마다 비용을 갱신
        for (int u = 0; u < n; u++) {
            if (cost[u][k] == INF) continue;
            for (int v = 0; v < n; v++)
                cost[u][v] = min(cost[u][v], cost[u][k] + cost[k][v]);
        }
    }
    
    // 최소 비용 계산
    long answer = INF;
    for (int i = 0; i < n; i++)
        answer = min(answer, cost[s-1][i] + cost[a-1][i] + cost[b-1][i]);
    return answer;
}

 

문제

  • 문제 링크
  • 문제 설명: 동영상 재생시간 길이 play_time, 공익광고의 재생시간 길이 adv_time, 시청자들이 해당 동영상을 재생했던 구간 정보 logs가 매개변수로 주어질 때, 시청자들의 누적 재생시간이 가장 많이 나오는 곳에 공익광고를 삽입하려고 합니다. 이때, 공익광고가 들어갈 시작 시각을 구해서 return 하도록 solution 함수를 완성해주세요. 만약, 시청자들의 누적 재생시간이 가장 많은 곳이 여러 곳이라면, 그 중에서 가장 빠른 시작 시각을 return 하도록 합니다.
    • 시작 시각이 여러 개면 가장 빠른 시작 시각을 반환
    • 누적 재생시간 = 재생 구간의 크기 * 재생한 사람 수

 

  • 입력
    • play_time := 00:00:01 이상 99:59:59 이하
    • adv_time <= play_time
    • logs := "H1:M1:S1-H2:M2:S2"
    • logs 크기 <= 300,000
  • 출력: 광고가 재생될 가장 빠른 시작 시각
play_time adv_time logs result
"02:03:55" "00:14:15" ["01:20:15-01:45:14", "00:40:31-01:00:00", "00:25:50-00:48:29", "01:30:59-01:53:29", "01:37:44-02:02:30"] "01:30:59"
"99:59:59" "25:00:00" ["69:59:59-89:59:59", "01:00:00-21:00:00", "79:59:59-99:59:59", "11:00:00-31:00:00"] "01:00:00"
"50:00:00" "50:00:00" ["15:36:51-38:21:49", "10:14:18-15:36:51", "38:21:49-42:51:45"] "00:00:00"

풀이

  • logs 배열의 크기가 최대 300,000 이므로 O(N) 만에 문제를 해결해야 함
  • 간단하게 누적 재생시간을 계산하려면 해당 시간대에 재생된 사람 수를 카운트 해야 함
  • play_time의 최대 시간을 초단위로 계산해보면 99*3600 + 59*60 + 59 = 359,999 로 배열의 크기가 크지 않음을 알 수 있음
  • 따라서 모든 시간을 초 단위로 바꾼 뒤 매 로그마다 재생된 구간에 사람 수를 카운트해서 슬라이딩 윈도우로 O(N)만에 가장 빠른 시각을 구할 수 있음
  • 주의할 점: 최대 로그 개수만큼 모든 구간이 재생되면 360000 * 300000 으로 32비트를 초과하므로 long long 자료형을 써야 함
#include <cstring>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;

// 시간 문자열을 초 단위로 변환하는 함수
int timeToSeconds(const string& time) {
    int hours = (time[0]-'0')*10 + (time[1]-'0');
    int minutes = (time[3]-'0')*10 + (time[4]-'0');
    int seconds = (time[6]-'0')*10 + (time[7]-'0');
    return hours*3600 + minutes*60 + seconds;
}

string solution(string play_time, string adv_time, vector<string> logs) {
    int seconds[360000];
    memset(seconds, 0, sizeof(seconds));
    for (auto& l : logs) {
        int start = timeToSeconds(l);
        int end = timeToSeconds(l.substr(9));
        for (int i = start; i < end; i++)
            seconds[i]++; // 끝 시간은 포함되지 않으므로 제외하고 재생 사람 수를 카운트
    }
    
    int playTime = timeToSeconds(play_time);
    int advTime = timeToSeconds(adv_time);
    int startTime = 0;
    ll sum = 0, maxSum;
    // 첫 광고시간동안 재생한 사람 수 누적합 계산
    for (int i = 0; i < advTime; i++) sum += seconds[i];
    maxSum = sum;
    // 한 칸씩 윈도우를 옮기면서 최댓값 탐색
    for (int i = advTime; i < playTime; i++) {
        sum = sum - seconds[i-advTime] + seconds[i];
        if (sum > maxSum) {
            startTime = i-advTime+1; // 최댓값이면 시작 시각 저장
            maxSum = sum;
        }
    }
    
    // 초 단위를 다시 문자열 포맷으로 변환
    string answer;
    int times[3] = {startTime / 3600, (startTime % 3600) / 60, (startTime % 3600) % 60};
    for (int i = 0; i < 3; i++) {
        int a = times[i] / 10, b = times[i] % 10;
        answer.push_back(a + '0');
        answer.push_back(b + '0');
        if (i < 2) answer.push_back(':');
    }
    
    return answer;
}

+ Recent posts