[SWEA] 2383. 점심 식사시간 - Simulation

제출일 : 2019-11-12

문제 풀이 시간 : 2D

난이도 : ★★★★★

Problem

link : https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV5V61LqAf8DFAWu

Constraints

  1. 시간제한 : 최대 50개 테스트 케이스를 모두 통과하는데, C/C++/Java 모두 3초
  2. 방의 한 변의 길이 N은 4 이상 10 이하의 정수이다. (4 ≤ N ≤ 10)
  3. 사람의 수는 1 이상 10 이하의 정수이다. (1 ≤ 사람의 수 ≤ 10)
  4. 계단의 입구는 반드시 2개이며, 서로 위치가 겹치지 않는다.
  5. 계단의 길이는 2 이상 10 이하의 정수이다. (2 ≤ 계단의 길이 ≤ 10)
  6. 초기에 입력으로 주어지는 사람의 위치와 계단 입구의 위치는 서로 겹치지 않는다.

input

입력의 맨 첫 줄에는 총 테스트 케이스의 개수 T가 주어지고, 그 다음 줄부터 T개의 테스트 케이스가 주어진다.

각 테스트 케이스의 첫 번째 줄에는 방의 한 변의 길이 N이 주어진다.

다음 N줄에는 N*N 크기의 지도의 정보가 주어진다.

지도에서 1은 사람을, 2 이상은 계단의 입구를 나타내며 그 값은 계단의 길이를 의미한다.

Output

테스트 케이스의 개수만큼 T줄에 T개의 테스트 케이스 각각에 대한 답을 출력한다.

각 줄은 "#x"로 시작하고 공백을 하나 둔 다음 정답을 출력한다. (x는 1부터 시작하는 테스트 케이스의 번호이다)

Example

input

10
5
0 1 1 0 0
0 0 1 0 3
0 1 0 1 0
0 0 0 0 0
1 0 5 0 0
5
0 0 1 1 0
0 0 1 0 2
0 0 0 1 0
0 1 0 0 0
1 0 5 0 0
…

output

#1 9
#2 8
…

Solution & Inpression

모의 SW 역량테스트문제

  1. 지도의 크기가 $N\times N$이고 사람, 계단의 길이가 주어진다.
    • 사람은 1 계단의 길이는 2 이상 10 이하
    • 계단의 개수는 2개이며 사람의 수는 최대 10명이다
  2. 각 사람은 두 계단 중 한곳에 도착하여, 계단을 내려간다.
    • 계단을 완전이 내려가는 시간은 계단의 길이 K 만큼의 시간이 소요된다.
    • 계단은 최대 3명까지 동시에 이용이 가능
    • 계단에 도착하면 1분 후에 용이 가능

계단의 개수가 2개로 고정되기때문에 중복순열을 이용하여 각 사람이 이용할 수있는 모든 경우를 구한뒤

해당 경우의 시간을 구해 문제를 해결하였다.

지금까지 풀었던 문제중에 가장 어려웠던 문제였다.

Code

언어 : JAVA

메모리 : 36,012 kb

실행시간 : 177 ms

import java.io.FileInputStream;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Scanner;

class Point { // 방에서의 위치를 나타내는 클래스
    int r, c;

    public Point(int r, int c) {
        this.r = r;
        this.c = c;
    }
}

public class Solution {
    static int N, M, S;
    static int match[];

    static Point[] man;
    static Point[] stair;
    static int[] length;

    static int answer;

    public static void main(String[] args) throws Exception {
        System.setIn(new FileInputStream("res/input.txt"));
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        for (int tc = 1; tc <= T; tc++) {
            N = sc.nextInt();
            M = 0; // 사람 수
            S = -1; // 계단 수

            man = new Point[10]; // 최대 사람은 10명(위치 좌표를 저장)
            stair = new Point[2]; // 계단은 2개(위치 좌표를 저장)
            length = new int[2];// 계단의 길이 저장
            for (int i = 1; i <= N; i++) {
                for (int j = 1; j <= N; j++) {
                    int tmp = sc.nextInt();
                    if (tmp == 1)
                        man[M++] = new Point(i, j);
                    else if (tmp != 0) {
                        stair[++S] = new Point(i, j);
                        length[S] = tmp;
                    }
                }
            }

            answer = Integer.MAX_VALUE;
            match = new int[M];
            dfs(0); // 모든 경우의 수를 구한다.
            System.out.println("#" + tc + " " + answer);
        }
        sc.close();
    }

    // 중복순열: 모든 경우의 수를 구한다 최대 2^10
    private static void dfs(int depth) {
        if (depth == M) {
            solve();
            return;
        }
        for (int i = 0; i < 2; i++) {
            match[depth] = i;
            dfs(depth + 1);
        }
    }

    // 각사람이 어느 계단을 이용할 지 정해졌을 때에 필요한 시간을 계산하는 함수
    private static void solve() {
        PriorityQueue<Integer> pq0 = new PriorityQueue<>();
        PriorityQueue<Integer> pq1 = new PriorityQueue<>();

        for (int i = 0; i < M; i++) {
            if (match[i] == 0)
                pq0.add(dist(man[i], stair[0]));
            else
                pq1.add(dist(man[i], stair[1]));
        }

        int man = M; // 남은 이용자.
        int[] stair0 = new int[3]; // 계단을 이용하는 사람들의 남은 이용시간.
        int[] stair1 = new int[3];

        int time = 0;
        while (true) {

            // 종료 조건 : 계단을 모든 이용자가 이용할 때 까지.
            if (man == 0) {
                boolean flag = true;
                for (int i = 0; i < 3; i++) {
                    if (stair0[i] != 0) {
                        flag = false;
                        break;
                    }
                    if (stair1[i] != 0) {
                        flag = false;
                        break;
                    }
                }
                if (flag)
                    break;
            }

            for (int i = 0; i < 3; i++) { // 계단은 최대 3명까지 동시에 이용할 수 있다.
                if (stair0[i] == 0) { // 현재 이용하는 사람이 없을때
                    if (!pq0.isEmpty()) { // 이용할 사람이 있고(큐가 비어있지 않다면)
                        if (pq0.peek() <= time) { // 도착했다면
                            man--; // 남은 이용자수를 내린다.
                            stair0[i] = length[0];// 해당 계단의 이동시간을 주고
                            pq0.poll();
                        }
                    }
                } else { // 현재 계단을 사용하고 있다면
                    stair0[i]--; // 값을 내리고
                    if (stair0[i] == 0) {// 계단을 이용하고 있지 않다면.
                        if (!pq0.isEmpty()) {
                            if (pq0.peek() <= time) {
                                man--; // 이용자를 내린다.
                                stair0[i] = length[0];
                                pq0.poll();
                            }
                        }
                    }
                }

                if (stair1[i] == 0) { // 현재 이용하는 사람이 없을때
                    if (!pq1.isEmpty()) { // 이용할 사람이 있고(큐가 비어있지 않다면)
                        if (pq1.peek() <= time) { // 도착했다면
                            man--; // 남은 이용자수를 내린다.
                            stair1[i] = length[1];// 해당 계단의 이동시간을 주고
                            pq1.poll();
                        }
                    }
                } else { // 현재 계단을 사용하고 있다면
                    stair1[i]--; // 값을 내리고
                    if (stair1[i] == 0) {// 계단을 이용하고 있지 않다면.
                        if (!pq1.isEmpty()) {
                            if (pq1.peek() <= time) {
                                man--; // 이용자를 내린다.
                                stair1[i] = length[1];
                                pq1.poll();
                            }
                        }
                    }
                }

            } // end of for

            time++;
        } // end of while

        if (time < answer)
            answer = time;
    }

    private static int dist(Point man, Point stair) {
        return Math.abs(man.r - stair.r) + Math.abs(man.c - stair.c);
    }

}

[SWEA] 2117. 홈 방범 서비스 - Simulation

제출일 : 2019-11-12

문제 풀이 시간 : 1H

난이도 : ★★

Problem

link : https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV5V61LqAf8DFAWu

Constraints

  1. 시간제한 : 최대 50개 테스트 케이스를 모두 통과하는데, C/C++/Java 모두 3초

  2. 도시의 크기 N은 5 이상 20 이하의 정수이다. (5 ≤ N ≤ 20)

  3. 홈방범 서비스의 운영 비용은 서비스 영역의 면적과 동일하다.

  4. 도시에는 최소 1개 이상의 집이 존재한다.

input

각 테스트 케이스의 첫 번째 줄에는 도시의 크기 N과 하나의 집이 지불할 수 있는 비용 M이 주어진다.

Output

테스트 케이스의 개수만큼 T줄에 T개의 테스트 케이스 각각에 대한 답을 출력한다.

출력해야 할 정답은 손해를 보지 않으면서 홈방범 서비스를 가장 많은 집들에 제공하는 서비스 영역을 찾았을 때,

Example

input

10
8 3
0 0 0 0 0 1 0 0
0 1 0 1 0 0 0 1
0 0 0 0 0 0 0 0
0 0 0 1 0 1 0 0
0 0 1 1 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 1 0 1 0
1 0 0 0 0 0 0 0
…

output

#1 5
…

Solution & Inpression

모의 SW 역량테스트문제 중 가장 쉬웠던 문제인것 같다.

핵심은 마름모를 만드는것.
$$
if ((Math.abs(i - r) + Math.abs(j - c)) <= k - 1)
$$

Code

언어 : JAVA

메모리 : 45,780 kb

실행시간 : 499 ms

import java.util.Scanner;

public class Solution {
    static int N, M;
    static int[][] map;
    static int max;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int T = sc.nextInt();
        for (int tc = 1; tc <= T; tc++) {
            N = sc.nextInt(); // 도시 크기
            M = sc.nextInt(); // 하나의 집이 지불할 수 있는 비용

            map = new int[N][N];
            int c = 0;
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    int tmp = sc.nextInt();
                    map[i][j] = tmp; // 집이면 1
                    if (tmp == 1) {
                        c++;
                    }
                }
            }

            max = Integer.MIN_VALUE;
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    if (c == max)
                        break;
                    solve(i, j);
                }
            }

            System.out.println("#" + tc + " " + max);
        }
        sc.close();
    }

    private static void solve(int r, int c) {
        for (int k = 1; k <= N + N; k++) {
            int cnt = 0;
            for (int i = 0; i < N; i++)
                for (int j = 0; j < N; j++)
                    if ((Math.abs(i - r) + Math.abs(j - c)) <= k - 1)
                        if (map[i][j] == 1)
                            cnt++;

            int cost = k * k + (k - 1) * (k - 1);
            if (cost <= cnt * M)
                if (max < cnt)
                    max = cnt;
        }
    }

}

[BOJ] 1600. 말이 되고픈 원숭이 - BFS

제출일 : 2019-11-12

문제 풀이 시간 : 1H 30M

난이도 : ★★★

Problem

link : https://www.acmicpc.net/problem/1600

동물원에서 막 탈출한 원숭이 한 마리가 세상구경을 하고 있다. 그 녀석은 말(Horse)이 되기를 간절히 원했다. 그래서 그는 말의 움직임을 유심히 살펴보고 그대로 따라 하기로 하였다. 말은 말이다. 말은 격자판에서 체스의 나이트와 같은 이동방식을 가진다. 다음 그림에 말의 이동방법이 나타나있다. x표시한 곳으로 말이 갈 수 있다는 뜻이다. 참고로 말은 장애물을 뛰어넘을 수 있다.

근데 원숭이는 한 가지 착각하고 있는 것이 있다. 말은 저렇게 움직일 수 있지만 원숭이는 능력이 부족해서 총 K번만 위와 같이 움직일 수 있고, 그 외에는 그냥 인접한 칸으로만 움직일 수 있다. 대각선 방향은 인접한 칸에 포함되지 않는다.

이제 원숭이는 머나먼 여행길을 떠난다. 격자판의 맨 왼쪽 위에서 시작해서 맨 오른쪽 아래까지 가야한다. 인접한 네 방향으로 한 번 움직이는 것, 말의 움직임으로 한 번 움직이는 것, 모두 한 번의 동작으로 친다. 격자판이 주어졌을 때, 원숭이가 최소한의 동작으로 시작지점에서 도착지점까지 갈 수 있는 방법을 알아내는 프로그램을 작성하시오.

Input

첫째 줄에 자연수 K가 주어진다. 둘째 줄에 격자판의 가로길이 W, 세로길이 H가 주어진다. 그 다음 H줄에 걸쳐 W개의 숫자가 주어지는데, 0은 아무것도 없는 평지, 1은 장애물을 뜻한다. 장애물이 있는 곳으로는 이동할 수 없다. 시작점과 도착점은 항상 평지이다. W와 H는 1이상 200이하의 자연수이고, K는 0이상 30이하의 정수이다.

Output

첫째 줄에 원숭이의 동작수의 최솟값을 출력한다. 시작점에서 도착점까지 갈 수 없는 경우엔 -1을 출력한다.

Example

input

1
3 4
0 0 0
1 1 0
1 1 1
1 0 0

output

5

Solution & Inpression

삼성 SW 역량 테스트 기출 문제

문제를 읽고 BFS문제라는것은 쉽게 파악이 가능했고 움직이는 dr배열도 아래와 같이 하면 해결할 수 있을것 같았다.

위의 테스트 케이스의 정답은 5이지만 결과가 -1로 나오기에 방문처리를 입력받은 K+1만큼을 만들어 사용하여 문제를 해결하였다.

Code

언어 : JAVA

메모리 : 118,864 kb

실행시간 : 932 ms

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    static int K, H, W;
    static int[][] map;
    static boolean[][][] visit;

    static final int[] dr = { -2, -1, 1, 2, 2, 1, -1, -2, -1, 1, 0, 0 };
    static final int[] dc = { 1, 2, 2, 1, -1, -2, -2, -1, 0, 0, -1, 1 };

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        K = sc.nextInt();
        W = sc.nextInt();
        H = sc.nextInt();

        map = new int[H][W];
        visit = new boolean[K + 1][H][W];
        for (int i = 0; i < H; i++) {
            for (int j = 0; j < W; j++) {
                map[i][j] = sc.nextInt();
            }
        }
        sc.close();

        if (!BFS(0, 0))
            System.out.println(-1);
    }

    private static boolean BFS(int i, int j) {
        Queue<int[]> q = new LinkedList<>();
        visit[K][i][j] = true;
        q.offer(new int[] { i, j, K, 0 });
        int k;
        while (!q.isEmpty()) {
            int[] cur = q.poll();

            if (cur[0] == H - 1 && cur[1] == W - 1) { // 도착
                System.out.println(cur[3]);
                return true;
            }

            if (cur[2] > 0) {
                k = 0; // 점프
            } else
                k = 8; // 한칸씩 움직이는것 부터

            for (; k < dr.length; k++) {
                int r = cur[0] + dr[k];
                int c = cur[1] + dc[k];
                if (isRange(r, c)) // 범위 안이고
                    if (map[r][c] == 0) // 장애물이 아닌경우에
                        if (k >= 8) { // 뛴경우가 아닐때
                            if (!visit[cur[2]][r][c]) {
                                visit[cur[2]][r][c] = true; // 방문해
                                q.offer(new int[] { r, c, cur[2], cur[3] + 1 }); // 기회는 그대로
                            }
                        } else {
                            if (!visit[cur[2] - 1][r][c]) {
                                visit[cur[2] - 1][r][c] = true; // 방문해
                                q.offer(new int[] { r, c, cur[2] - 1, cur[3] + 1 }); // 기회 -1
                            }
                        }
            }
        } // end of while
        return false;
    }

    private static boolean isRange(int r, int c) {
        if (0 <= r && r < H && 0 <= c && c < W)
            return true;
        return false;
    }

}

[BOJ] 17779. 게리맨더링2 - BF

제출일 : 2019-11-12

문제 풀이 시간 : 4H

난이도 : ★★★★☆

Problem

link : https://www.acmicpc.net/problem/17779

 

재현시의 시장 구재현은 지난 몇 년간 게리맨더링을 통해서 자신의 당에게 유리하게 선거구를 획정했다. 견제할 권력이 없어진 구재현은 권력을 매우 부당하게 행사했고, 심지어는 시의 이름도 재현시로 변경했다. 이번 선거에서는 최대한 공평하게 선거구를 획정하려고 한다.

재현시는 크기가 N×N인 격자로 나타낼 수 있다. 격자의 각 칸은 구역을 의미하고, r행 c열에 있는 구역은 (r, c)로 나타낼 수 있다. 구역을 다섯 개의 선거구로 나눠야 하고, 각 구역은 다섯 선거구 중 하나에 포함되어야 한다. 선거구는 구역을 적어도 하나 포함해야 하고, 한 선거구에 포함되어 있는 구역은 모두 연결되어 있어야 한다. 구역 A에서 인접한 구역을 통해서 구역 B로 갈 수 있을 때, 두 구역은 연결되어 있다고 한다. 중간에 통하는 인접한 구역은 0개 이상이어야 하고, 모두 같은 선거구에 포함된 구역이어야 한다.

선거구를 나누는 방법은 다음과 같다.

  1. 기준점 (x, y)와 경계의 길이 d1, d2를 정한다. (d1, d2 ≥ 1, 1 ≤ x < x+d1+d2 ≤ N, 1 ≤ y-d1 < y < y+d2 ≤ N)
  2. 다음 칸은 경계선이다.
    1. (x, y), (x+1, y-1), ..., (x+d1, y-d1)
    2. (x, y), (x+1, y+1), ..., (x+d2, y+d2)
    3. (x+d1, y-d1), (x+d1+1, y-d1+1), ... (x+d1+d2, y-d1+d2)
    4. (x+d2, y+d2), (x+d2+1, y+d2-1), ..., (x+d2+d1, y+d2-d1)
  3. 경계선과 경계선의 안에 포함되어있는 5번 선거구이다.
  4. 5번 선거구에 포함되지 않은 구역 (r, c)의 선거구 번호는 다음 기준을 따른다.
    • 1번 선거구: 1 ≤ r < x+d1, 1 ≤ c ≤ y
    • 2번 선거구: 1 ≤ r ≤ x+d2, y < c ≤ N
    • 3번 선거구: x+d1 ≤ r ≤ N, 1 ≤ c < y-d1+d2
    • 4번 선거구: x+d2 < r ≤ N, y-d1+d2 ≤ c ≤ N

아래는 크기가 7×7인 재현시를 다섯 개의 선거구로 나눈 방법의 예시이다.

img img img
x = 2, y = 4, d1 = 2, d2 = 2 x = 2, y = 5, d1 = 3, d2 = 2 x = 4, y = 3, d1 = 1, d2 = 1

구역 (r, c)의 인구는 $A[r][c]$이고, 선거구의 인구는 선거구에 포함된 구역의 인구를 모두 합한 값이다. 선거구를 나누는 방법 중에서, 인구가 가장 많은 선거구와 가장 적은 선거구의 인구 차이의 최솟값을 구해보자.

Input

첫째 줄에 재현시의 크기 N이 주어진다.

둘째 줄부터 N개의 줄에 N개의 정수가 주어진다. r행 c열의 정수는 $A[r][c]$를 의미한다.

Output

첫째 줄에 인구가 가장 많은 선거구와 가장 적은 선거구의 인구 차이의 최솟값을 출력한다.

Constraints

  • 5 ≤ N ≤ 20
  • 1 ≤ $A[r][c]$ ≤ 100

Example

input

6
1 2 3 4 1 6
7 8 9 1 4 2
2 3 4 1 1 3
6 6 6 6 9 4
9 1 9 1 9 5
1 1 1 1 9 9

output

18

Solution & Inpression

삼성 SW 역량 테스트 기출 문제

2차원 배열을 5구역으로 나누는 부분에서 많이 방황하였다. 어렵다..... 인덱스 조작,

Code

언어 : JAVA

메모리 : 20,204 kb

실행시간 : 312 ms

import java.util.Scanner;

public class Main {
    static int N, min;
    static int[][] map;
    static int[] point;
    static int[] sum;

    public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    N = sc.nextInt();
    map = new int[N][N];
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
        map[i][j] = sc.nextInt();
        }
    }
    sc.close();
    min = Integer.MAX_VALUE;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
        point = new int[] { i, j };
        solve(point);
        }
    }
    System.out.println(min);
    }

    private static void solve(int[] point) {
    for (int d1 = 0; d1 < N; d1++) {
        for (int d2 = 0; d2 < N; d2++) {
        if ((0 <= d1 && 0 <= d2) && (0 <= point[0] && point[0] < point[0] + d1 + d2 && point[0] + d1 + d2 < N)
            && (0 <= point[1] - d1 && point[1] - d1 < point[1] && point[1] < point[1] + d2
                && point[1] + d2 < N)) {
            // System.out.println(Arrays.toString(point) + " " + d1 + " " + d2);
            int res = div(d1, d2);
            if (min > res)
            min = res;
        }
        }
    }
    }

    private static int div(int d1, int d2) {
    sum = new int[5];
    for (int r = 0; r < N; r++) {
        for (int c = 0; c < N; c++) {
        if ((Math.abs(r - 0) + Math.abs(c - 0)) >= point[0] + point[1])
            if ((Math.abs(r - 0) + Math.abs(c - 0)) <= point[0] + point[1] + 2 * d2)
            if ((Math.abs(r - 0) + Math.abs(c - N)) >= (N - point[1]) + point[0])
                if ((Math.abs(r - 0) + Math.abs(c - N)) <= (N - point[1]) + point[0] + 2 * d1) {
                // map[r][c] = 5;
                sum[4] += map[r][c];
                continue;
                }

        if (0 <= r && r < point[0] + d1 && 0 <= c && c <= point[1]) {
            // map[r][c] = 1;
            sum[0] += map[r][c];
        } else if (0 <= r && r <= point[0] + d2 && point[1] < c && c < N) {
            // map[r][c] = 2;
            sum[1] += map[r][c];
        } else if (point[0] + d1 <= r && r <= N && 0 <= c && c < point[1] - d1 + d2) {
            // map[r][c] = 3;
            sum[2] += map[r][c];
        } else if (point[0] + d2 <= r && r <= N && point[1] - d1 + d2 <= c && c < N) {
            // map[r][c] = 4;
            sum[3] += map[r][c];
        }
        }
    }
    int min = sum[0];
    int max = sum[0];
    for (int i = 1; i < 5; i++) {
        if (min > sum[i])
        min = sum[i];
        if (max < sum[i])
        max = sum[i];
    }

    return max - min;
    }

}

[BOJ] 17825. 주사위 윷놀이 - 중복순열

제출일 : 2019-11-11

문제 풀이 시간 : 5H

난이도 : ★★★★☆

Problem

link : https://www.acmicpc.net/problem/17825

주사위 윷놀이는 다음과 같은 게임판에서 하는 게임이다.

img

가장 처음에는 시작에 말 4개가 있다. 말은 게임판에 적힌 화살표의 방향대로만 이동할 수 있다. 파란색 칸에서 말이 이동을 시작하는 경우에는 파란색 화살표의 방향으로 이동해야 하며 파란색 칸을 지나가는 경우에는 빨간 화살표의 방향대로 이동해야 한다.

게임은 1부터 5까지 한 면에 하나씩 적혀있는 5면 주사위를 굴려서 나온 수만큼 이동하는 방식으로 진행한다. 이동하려고 하는 칸에 말이 이미 있는 경우에는 그 칸으로 이동할 수 없다. 시작과 도착칸은 말이 이미 있어도 이동할 수 있다. 말이 이동을 마칠때마다 칸에 적혀있는 수가 점수에 추가된다.

말이 도착으로 이미 이동한 경우에는 더 이상 이동할 수 없고, 말이 이동하려고 하는 칸이 도착을 넘어가는 경우에는 도착에서 이동을 마친다.

주사위에서 나올 수 10개를 미리 알고있을때, 얻을 수 있는 점수의 최댓값을 구해보자.

Input

첫째 줄에 주사위에서 나올 수 10개가 순서대로 주어진다.

Output

얻을 수 있는 점수의 최댓값을 출력한다.

Example

input

1 2 3 4 1 2 3 4 1 2

output

190

Solution & Inpression

삼성 SW 역량 테스트 기출 문제

움직일수 있는 길은 4가지 경우로

모든 경우의 수($4^{10}=1,048,576$)를 중복순열을 이용하여 구한뒤 중복되는 경우는 버리고 최대값을 구하여 해결하였다.

중복되는 경우의 수가 일반적인 경우에는 움직이고 있는길과 현재 위치가 같을 경우이지만

마지막 25, 30, 34, 40은 움직이는 길이 다르더라도 같은 위치일 수 있다

이경우는 하드코딩으로 예외처리를 하였다.

이 문제를 풀며 난관에 여러번 부딛혔는다.

  1. 문제를 이해하는데 많은 시간이 소요되었고
  2. 중복을 처리할때 같은 경로가 아니더라도 같은 위치일 수 있고
  3. 중복되는 경우는 제외해야 한다.

위 3가지를 고려했다면 더 빠른 시간내에 해결 할 수 있지 않았을까??

Code

언어 : JAVA

메모리 : 158,884 kb

실행시간 : 1,124 ms

import java.util.Scanner;

public class Main {
    static int[] route0 = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40 };
    static int[] route1 = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 28, 27, 26, 25, 30, 35, 40 };
    static int[] route2 = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 25, 30, 35, 40 };
    static int[] route3 = { 0, 2, 4, 6, 8, 10, 13, 16, 19, 25, 30, 35, 40 };

    static int[] cmd;
    static int[] move;
    static int[][] horse;

    static int max;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        cmd = new int[10];
        for (int i = 0; i < 10; i++) { // 입력되는 명령어의 수 10개
            cmd[i] = sc.nextInt();
        }
        sc.close();
        // 입력 끝

        move = new int[10];
        max = Integer.MIN_VALUE;
        DFS(0);
        System.out.println(max);
    }

    private static void DFS(int depth) {
        if (depth == cmd.length) {
            int sum = solve();
            if (max < sum)
                max = sum;
            return;
        }

        for (int i = 0; i < 4; i++) {
            move[depth] = i;
            DFS(depth + 1);
        }

    }

    private static int solve() {
        horse = new int[4][2]; // 말의 위치, 가는 길, 움직인 횟수
        int sum = 0;

        for (int i = 0; i < 10; i++) {
            int cur = move[i]; // 현재움직일 말의 번호.
            horse[cur][0] += cmd[i]; // 움직였을때의 위치.
            int tmp = horse[cur][1];

            // ================길 변경================
            if (horse[cur][1] == 0) {
                if (horse[cur][0] == 5)
                    horse[cur][1] = 3;
                else if (horse[cur][0] == 10)
                    horse[cur][1] = 2;
                else if (horse[cur][0] == 15)
                    horse[cur][1] = 1;
            }

            // ================중복체크================
            boolean flag = false;
            int j = 0;
            for (; j < horse.length; j++) {
                if (cur != j) {
                    if (horse[cur][0] == horse[j][0] && horse[cur][1] == horse[j][1]) {
                        flag = true;
                        break;
                    } else if ((horse[cur][1] == 1 && horse[cur][0] == 19) || (horse[cur][1] == 2 && horse[cur][0] == 13)
                            || (horse[cur][1] == 3 && horse[cur][0] == 9)) {
                        // 25번에 도착했을때
                        if (horse[j][1] == 1 && horse[j][0] == 19) {// 1번길에 같은위치
                            flag = true;
                            break;
                        } else if (horse[j][1] == 2 && horse[j][0] == 13) {// 2번길에 같은위치
                            flag = true;
                            break;
                        } else if (horse[j][1] == 3 && horse[j][0] == 9) {// 3번길에 같은위치
                            flag = true;
                            break;
                        }
                    } else if ((horse[cur][1] == 1 && horse[cur][0] == 20) || (horse[cur][1] == 2 && horse[cur][0] == 14)
                            || (horse[cur][1] == 3 && horse[cur][0] == 10)) {
                        // 30번에 도착했을때
                        if (horse[j][1] == 1 && horse[j][0] == 20) {// 1번길에 같은위치
                            flag = true;
                            break;
                        } else if (horse[j][1] == 2 && horse[j][0] == 14) {// 2번길에 같은위치
                            flag = true;
                            break;
                        } else if (horse[j][1] == 3 && horse[j][0] == 10) {// 3번길에 같은위치
                            flag = true;
                            break;
                        }
                    }

                    else if ((horse[cur][1] == 1 && horse[cur][0] == 21) || (horse[cur][1] == 2 && horse[cur][0] == 15)
                            || (horse[cur][1] == 3 && horse[cur][0] == 11)) {
                        // 35번에 도착했을때
                        if (horse[j][1] == 1 && horse[j][0] == 21) {// 1번길에 같은위치
                            flag = true;
                            break;
                        } else if (horse[j][1] == 2 && horse[j][0] == 15) {// 2번길에 같은위치
                            flag = true;
                            break;
                        } else if (horse[j][1] == 3 && horse[j][0] == 11) {// 3번길에 같은위치
                            flag = true;
                            break;
                        }
                    } else if ((horse[cur][1] == 0 && horse[cur][0] == 20) || (horse[cur][1] == 1 && horse[cur][0] == 22)
                            || (horse[cur][1] == 2 && horse[cur][0] == 16) || (horse[cur][1] == 3 && horse[cur][0] == 12)) {
                        // 40번에 도착했을때
                        if (horse[j][1] == 0 && horse[j][0] == 20) {// 0번길에 같은위치
                            flag = true;
                            break;
                        } else if (horse[j][1] == 1 && horse[j][0] == 22) {// 1번길에 같은위치
                            flag = true;
                            break;
                        } else if (horse[j][1] == 2 && horse[j][0] == 16) {// 2번길에 같은위치
                            flag = true;
                            break;
                        } else if (horse[j][1] == 3 && horse[j][0] == 12) {// 3번길에 같은위치
                            flag = true;
                            break;
                        }
                    }
                }
            }
            if (flag) {
                return Integer.MIN_VALUE;
            }

            // ================더하기================
            if (horse[cur][1] == 0 && horse[cur][0] < route0.length)
                sum += route0[horse[cur][0]];
            else if (horse[cur][1] == 1 && horse[cur][0] < route1.length)
                sum += route1[horse[cur][0]];
            else if (horse[cur][1] == 2 && horse[cur][0] < route2.length)
                sum += route2[horse[cur][0]];
            else if (horse[cur][1] == 3 && horse[cur][0] < route3.length)
                sum += route3[horse[cur][0]];
        }
        return sum;
    }

}

[BOJ] 17822. 원판돌리기 - Simulation(BFS)

제출일 : 2019-11-11

문제 풀이 시간 : 1H 30M

난이도 : ★★★☆

Problem

link : https://www.acmicpc.net/problem/17822

반지름이 1, 2, ..., N인 원판이 크기가 작아지는 순으로 바닥에 놓여있고, 원판의 중심은 모두 같다. 각각의 원판에는 M개의 정수가 적혀있고, i번째 원판에 적힌 j번째 수의 위치는 (i, j)로 표현한다. 수의 위치는 다음을 만족한다.

  • (i, 1)은 (i, 2), (i, M)과 인접하다.
  • (i, M)은 (i, M-1), (i, 1)과 인접하다.
  • (i, j)는 (i, j-1), (i, j+1)과 인접하다. (2 ≤ j ≤ M-1)
  • (1, j)는 (2, j)와 인접하다.
  • (N, j)는 (N-1, j)와 인접하다.
  • (i, j)는 (i-1, j), (i+1, j)와 인접하다. (2 ≤ i ≤ N-1)

아래 그림은 N = 3, M = 4인 경우이다.

img

원판의 회전은 독립적으로 이루어진다. 2번 원판을 회전했을 때, 나머지 원판은 회전하지 않는다. 원판을 회전시킬 때는 수의 위치를 기준으로 하며, 회전시킨 후의 수의 위치는 회전시키지 전과 일치해야 한다.

다음 그림은 원판을 회전시킨 예시이다.

img img img
1번 원판을 시계 방향으로 1칸 회전 2, 3번 원판을 반시계 방향으로 3칸 회전 1, 3번 원판을 시계 방향으로 2칸 회전

원판을 아래와 같은 방법으로 총 T번 회전시키려고 한다. 원판의 회전 방법은 미리 정해져 있고, i번째 회전할때 사용하는 변수는 xi, di, ki이다.

  1. 번호가 xi의 배수인 원판을 di방향으로 ki칸 회전시킨다. di가 0인 경우는 시계 방향, 1인 경우는 반시계 방향이다.
  2. 인접하면서 수가 같은 것을 모두 찾는다.
    1. 그러한 수가 있는 경우에는 원판에서 인접하면서 같은 수를 모두 지운다.
    2. 없는 경우에는 원판에 적힌 수의 평균을 구하고, 평균보다 큰 수에서 1을 빼고, 작은 수에는 1을 더한다.

원판을 T번 회전시킨 후 원판에 적힌 수의 합을 구해보자.

Input

첫째 줄에 N, M, T이 주어진다.

둘째 줄부터 N개의 줄에 원판에 적힌 수가 주어진다. i번째 줄의 j번째 수는 (i, j)에 적힌 수를 의미한다.

다음 T개의 줄에 xi, di, ki가 주어진다.

Output

원판을 T번 회전시킨 후 원판에 적힌 수의 합을 출력한다.

Constraints

  • 2 ≤ N, M, T ≤ 50
  • 1 ≤ 원판에 적힌 수 ≤ 1,000
  • 2 ≤ xi ≤ N
  • 0 ≤ di ≤ 1
  • 1 ≤ ki < M

Example

input

4 4 1
1 1 2 3
5 2 4 2
3 1 3 5
2 1 3 2
2 0 1

output

30

Solution & Inpression

삼성 SW 역량 테스트 기출 문제

시뮬레이션으로 문제에 주어진데로 풀면 되지만 조건이 까다롭고 어려웠던 문제.

Code

언어 : JAVA

메모리 : 30,488 kb

실행시간 : 280 ms

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    static int N, M, T;
    static int[][] map;
    static int[] dr = { -1, 1, 0, 0 };
    static int[] dc = { 0, 0, -1, 1 };

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        N = sc.nextInt() + 1; // 원판의 개수
        M = sc.nextInt(); // 각 원판의 정수의 개수
        T = sc.nextInt(); // 테스트 케이스 개수

        map = new int[N][M];

        for (int i = 1; i < N; i++) {
            for (int j = 0; j < M; j++) {
                map[i][j] = sc.nextInt();
            }
        }
        // 입력끝
        for (int i = 0; i < T; i++) {
            int x = sc.nextInt(); // 번호가 x의 배수인 원판을
            int d = sc.nextInt(); // d방향으로 (0:시계방향, 1:반시계방향)
            int k = sc.nextInt(); // k칸 회전시킨다.
            rotate(x, d, k);
            if (!check())
                edit();
        }
        System.out.println(sum());

        sc.close();
    }

    private static void edit() {
        int sum = 0, cnt = 0;
        for (int i = 1; i < N; i++) {
            for (int j = 0; j < M; j++) {
                if (map[i][j] != 0) {
                    cnt++;
                    sum += map[i][j];
                }
            }
        }
        if (cnt == 0)
            return;
        double avg = sum / (double) cnt;
        for (int i = 1; i < N; i++) {
            for (int j = 0; j < M; j++) {
                if (map[i][j] != 0) {
                    if (map[i][j] > avg) // 평균보다 큰수
                        map[i][j]--; // 1을 빼고
                    else if (map[i][j] < avg)
                        map[i][j]++; // 1을 더한다
                }
            }
        }
    }

    private static boolean check() {
        boolean flag = false;
        for (int i = 1; i < N; i++) {
            for (int j = 0; j < M; j++) {
                if (map[i][j] != 0) {
                    if (bfs(i, j, map[i][j])) {
                        map[i][j] = 0;
                        flag = true;
                    }
                }
            }
        }
        return flag;
    }

    private static boolean bfs(int i, int j, int k) {
        Queue<int[]> q = new LinkedList<>();
        boolean flag = false;
        q.offer(new int[] { i, j });

        while (!q.isEmpty()) {
            int[] cur = q.poll();
            for (int dir = 0; dir < 4; dir++) {
                int r = cur[0] + dr[dir];
                int c = cur[1] + dc[dir];
                if (c == -1)
                    c = M - 1;
                else if (c == M)
                    c = 0;
                if (isRange(r, c)) {
                    if (map[r][c] == k) {
                        q.offer(new int[] { r, c });
                        map[r][c] = 0;
                        flag = true;
                    }
                }
            }
        }
        return flag;
    }

    private static boolean isRange(int r, int c) {
        if (1 <= r && r < N)
            return true;
        return false;
    }

    private static int sum() {
        int sum = 0;
        for (int i = 1; i < N; i++) {
            for (int j = 0; j < M; j++) {
                sum += map[i][j];
            }
        }
        return sum;
    }

    private static void rotate(int x, int d, int k) {
        for (int num = x; num < N; num += x) {
            int cnt = k;
            while (cnt-- != 0) { // k칸 회전
                int[] tmp = map[num].clone();// 맵 복사
                switch (d) {
                case 0: // 시계방향 회전(오른쪽으로)
                    for (int j = 0; j < M - 1; j++) {
                        map[num][j + 1] = tmp[j];
                    }
                    map[num][0] = tmp[M - 1];
                    break;
                case 1: // 반시계방향 회전(왼쪽으로)
                    for (int j = 0; j < M - 1; j++) {
                        map[num][j] = tmp[j + 1];
                    }
                    map[num][M - 1] = tmp[0];
                    break;
                }
            }
        }
    }

}

[Algorithm] 최소신장트리(MST)

신장트리

n개의 정점으로 이루어진 무향 그래프에서 n개의 정점과 n-1개의 간선들로 이루어진 트리

최소신장트리(MST, Minimum Spanning Tree)

무향 가중치 그래프에서 신장트리를 구성하는 간선들의 가중치의 합이 최소인 신장 트리

Prim Algorithm

Kruskal Algorithm

Reference

『IT CookBook, 자바로 배우는 쉬운 자료구조』, 이지영 집필, 한빛아카데미(2008)

『 SW 문제해결 응용 - 구현 - 그래프의 최소 비용 문제』

'Algorithm' 카테고리의 다른 글

삽입정렬(Insertion Sort)  (0) 2020.07.19
거품정렬(Bubble Sort)  (0) 2020.07.19
[Algorithm] 최소신장트리(MST) - Kruskal  (0) 2019.11.10
[Algorithm] 최소신장트리(MST) - Prim  (0) 2019.11.10
PowerSet(부분집합)  (0) 2019.10.19

[Algorithm] 최소신장트리(MST) - Kruskal

간선을 하나씩 선택해서 MST를 찾는 알고리즘

  1. 최소, 모든 간선을 가중치에 따라 오름차순으로 정렬
  2. 가중치가 가장 낮은 간선부터 선택하면서 트리를 증가시킴
    • 사이클이 존재하면 다음으로 가중치가 낮은 간선 선택
  3. n-1개의 간선이 선택될 때 까지 2를 반복

code

Edge

public class Edge {
    int adjvertex; // 간선의 다른쪽 끝 정점
    int weight;    // 간선의 가중치
    public Edge(int v, int wt) {
        adjvertex = v;
        weight    = wt;
    }
}

KruskalMST

import java.util.*;
public class KruskalMST {
    int N, M; // 그래프 정점, 간선의 수
    List<Edge>[] graph;
    UnionFind uf; // Union-Find 연산을 사용하기 위해    
    Edge[] tree;    
    static class Weight_Comparison implements Comparator<Edge> { //weight를 기준으로 우선순위큐를  사용하기 위해
        public int compare(Edge e, Edge f) {
            if(e.weight > f.weight)
                return 1;
            else if(e.weight < f.weight)
                return -1;        
            return 0;
        }        
    }
    public KruskalMST(List<Edge>[] adjList, int numOfEdges) {
        N = adjList.length;
        M = numOfEdges;
        graph = adjList;
        uf = new UnionFind(N);    // Union-Find 연산을 사용하기 위해    
        tree = new Edge[N-1];
    }
    public Edge[] mst()    {  // Kruskal 알고리즘        
        Weight_Comparison BY_WEIGHT = new Weight_Comparison();  // 우선순위큐를  weight 기준으로 구성하기 위해        
        PriorityQueue<Edge> pq = new PriorityQueue<Edge>(M, BY_WEIGHT);  // 자바 라이브러리의 우선순위큐 사용
                                    // 우선순위큐의 크기로 M(간선의 수)을 지정, BY_WEIGHT는 line 24의 comparator
        for (int i = 0; i < N; i++){ 
            for (Edge e : graph[i]){
                pq.add(e);  // edgeArray의 간선 객체들을 pq에 삽입
            }
        }        
        int count = 0;    
        while (!pq.isEmpty() && count < N-1) {
            Edge e = pq.poll();          // 최소 가중치를 가진 간선를 pq에서 제거하고 가져옴
            int u = e.vertex;            // 가져온 간선의 한 쪽 정점
            int v = e.adjvertex;          // 가져온 간선의 다른 한 쪽 정점
            if (!uf.isConnected(u, v)) { // v와 w가 각각 다른 집합에 속해 있으면
                uf.union(u, v);          // v가 속한 집합과 u가 속한 집합의 합집합 수행
                tree[count++] = e;       // e를 MST의 간선으로서 tree에 추가
            }
        }            
        return tree;
    }
}

UnionFind

public class UnionFind {  
    protected  int[] p;    // 배열크기는 정점의 수 N이고 p[i]는 i의 부모 원소를 저장한다. 
    protected  int[] rank; 

    public UnionFind(int N) {
        p = new int[N];
        rank = new int[N];
        for (int i = 0; i < N; i++) {
            p[i]    = i;  // 초기엔 N개의 트리가 각각 i 자기 자신이 부모이기 떄문에 
            rank[i] = 0;  // 초기엔 N개의 트리 각각의 rank를 0으로 초기화 
        }
    }
    //i가 속한 집합의 루트노드를 재귀적으로 찾고 최종적으로 경로상의 각 원소의 부모를 루트노드로 만든다.
    protected int find(int i) { // 경로 압축
        if ( i != p[i])   
            p[i] = find(p[i]);  //리턴하며 경로상의 각 노드의 부모가 루트가되도록 만든다.
        return p[i];
    }
    //i와 j가 같은 트리에 있는지를 검사
    public boolean isConnected(int i, int j) {
        return find(i) == find(j); 
    }
    public void union(int i, int j) {  // Union 연산
        int iroot = find(i);
        int jroot = find(j);
        if (iroot == jroot) return;  // 루트노드가 동일하면 더이상의 수행없이 그대로 리턴
        // rank가 높은 루트노드가 승자로 union을 수행한다.
        if (rank[iroot] > rank[jroot]) 
            p[jroot] = iroot;               // iroot가 승자
        else if (rank[iroot] < rank[jroot]) 
            p[iroot] = jroot;               // jroot가 승자
        else {
            p[jroot] = iroot;  // 둘중에 하나 임의로 승자
            rank[iroot]++;     // iroot의 rank 1 증가
        }
    }
}

수행시간

간선을 정렬(또는 우선순위큐의 삽입과 삭제)하는데 소요되는 시간 $O(MlogM) = O(MlogN)$

신장트리가 만들어질 때까지 간선에 대해 isConnected()와 union()을 수행하는 시간 $O((M+N)logN)$
$$
O(MlogN) + O((M+N)logN) = O(MlogN)
$$

Reference

『IT CookBook, 자바로 배우는 쉬운 자료구조』, 이지영 집필, 한빛아카데미(2008)

『SW 문제해결 응용 - 구현 - 그래프의 최소 비용 문제』
https://swexpertacademy.com/main/learn/course/subjectDetail.do?subjectId=AV3GBeD6AF4BBARB#

'Algorithm' 카테고리의 다른 글

거품정렬(Bubble Sort)  (0) 2020.07.19
[Algorithm] 최소신장트리(MST)  (0) 2019.11.10
[Algorithm] 최소신장트리(MST) - Prim  (0) 2019.11.10
PowerSet(부분집합)  (0) 2019.10.19
Disjoint-Set (서로소 집합)  (0) 2019.10.15