[SWEA] 2382. [모의 SW 역량테스트] 미생물 격리 (Java)

Problem

제출일 : 2020-03-05

문제 풀이 시간 : 1H 30M

난이도 : ★★★☆


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

정사각형 구역 안에 K개의 미생물 군집이 있다.

이 구역은 가로 N개, 세로 N개, 총 N * N 개의 동일한 크기의 정사각형 셀들로 이루어져 있다.

미생물들이 구역을 벗어나는걸 방지하기 위해, 가장 바깥쪽 가장자리 부분에 위치한 셀들에는 특수한 약품이 칠해져 있다.

[Fig. 1]은 9개의 군집이 한 변이 7개의 셀로 이루어진 구역에 배치되어 있는 예이다.

가장자리의 빨간 셀은 약품이 칠해져 있는 셀이다.

img


[Fig. 1]

① 최초 각 미생물 군집의 위치와 군집 내 미생물의 수, 이동 방향이 주어진다. 약품이 칠해진 부분에는 미생물이 배치되어 있지 않다. 이동방향은 상, 하, 좌, 우 네 방향 중 하나이다.

② 각 군집들은 1시간마다 이동방향에 있는 다음 셀로 이동한다.

③ 미생물 군집이 이동 후 약품이 칠해진 셀에 도착하면 군집 내 미생물의 절반이 죽고, 이동방향이 반대로 바뀐다.
미생물 수가 홀수인 경우 반으로 나누어 떨어지지 않으므로, 다음과 같이 정의한다.

살아남은 미생물 수 = 원래 미생물 수를 2로 나눈 후 소수점 이하를 버림 한 값

따라서 군집에 미생물이 한 마리 있는 경우 살아남은 미생물 수가 0이 되기 때문에, 군집이 사라지게 된다,

④ 이동 후 두 개 이상의 군집이 한 셀에 모이는 경우 군집들이 합쳐지게 된다.
합쳐 진 군집의 미생물 수는 군집들의 미생물 수의 합이며, 이동 방향은 군집들 중 미생물 수가 가장 많은 군집의 이동방향이 된다.
합쳐지는 군집의 미생물 수가 같은 경우는 주어지지 않으므로 고려하지 않아도 된다.

M 시간 동안 이 미생물 군집들을 격리하였다. M시간 후 남아 있는 미생물 수의 총합을 구하여라.

시간에 지남에 따라 군집이 변하는 예를 보자.

[Fig. 2]은 최초 군집의 배치를 그림으로 표현한 것이다. 이는 예제 입력 1번과 동일하다. (N = 7, K = 9)

img


[Fig. 2]

1시간 후 [Fig. 3]과 같이 군집들이 이동한다.

A 군집은 약품이 칠해진 구역(가장 자리의 빨간 셀)로 이동하게 되어, 미생물 중3마리만 남고 나머지는 죽는다. 이동 방향은 상에서 하로 바뀐다.

D, E, F 군집은 모두 세로 위치 3, 가로 위치 3에 위치한 셀로 모이게 되며, 합쳐진 군집의 미생물 수는 8 + 14 + 3 = 25가 된다.

E 군집의 미생물 수가 가장 많기 때문에, 합쳐 진 군집의 이동 방향은 E 군집의 이동 방향인 상이 된다.

G, H 군집도 세로 위치 2, 가로 위치 5에 위치한 셀로 모이게 되며, 미생물 수는 108이 되고 이동 방향은 상이 된다.

img


[Fig. 3]

시작으로부터 2시간이 지났을 때, [Fig. 4]와 같이 군집들이 이동한다.

A, B 그룹은 이동 중 섞이지 않고 각 그룹의 이동 방향으로 움직이는데, B 그룹은 약품이 칠해진 셀로 이동하므로 미생물 수가 7에서 3으로 반감하고, 이동 방향이 상에서 하로 바뀐다.

img


[Fig. 4]

2시간이 지난 후, 남아 있는 미생물 수는 총 3 + 3 + 5 + 25 + 108 + 1 = 145이다.

Constraints

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

  2. 구역의 모양은 정사각형으로 주어지며, 한 변의 셀의 개수 N은 5이상 100이하의 정수이다. (5 ≤ N ≤ 100)

  3. 최초 배치되어 있는 미생물 군집의 개수 K는 5이상 1,000이하의 정수이다. (5 ≤ K ≤ 1,000)

  4. 격리 시간 M은 1이상 1,000이하의 정수이다. (1 ≤ M ≤ 1,000)

  5. 최초 약품이 칠해진 가장자리 부분 셀에는 미생물 군집이 배치되어 있지 않다.

  6. 최초에 둘 이상의 군집이 동일한 셀에 배치되는 경우는 없다.

  7. 각 군집 내 미생물 수는 1 이상 10,000 이하의 정수이다.

  8. 군집의 이동방향은 상하좌우 4방향 중 한 방향을 가진다. (상: 1, 하: 2, 좌: 3, 우: 4)

  9. 주어진 입력으로 진행하였을 때, 동일한 셀에 같은 미생물 수를 갖는 두 군집이 모이는 경우는 발생하지 않는다.

  10. 각 군집의 정보는 세로 위치, 가로 위치, 미생물 수, 이동 방향 순으로 주어진다. 각 위치는 0번부터 시작한다.

Input

첫 줄에는 총 테스트 케이스의 개수 T가 주어진다.

그 다음 줄부터 T개의 테스트 케이스가 차례대로 주어진다.

각 테스트 케이스의 첫째 줄에는 구역의 한 변에 있는 셀의 개수 N, 격리 시간 M, 미생물 군집의 개수 K가 순서대로 주어지며, 다음 K줄에 걸쳐서 미생물 군집 K개의 정보가 주어진다.

미생물 군집의 정보는 세로 위치, 가로 위치, 미생물 수, 이동 방향 순으로 4개의 정수가 주어진다.

Output

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

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

출력해야 할 정답은 M시간 후 남아 있는 미생물 수의 총 합이다.

Example

input

10      
7 2 9   
1 1 7 1 
2 1 7 1
5 1 5 4
3 2 8 4 
4 3 14 1
3 4 3 3 
1 5 8 2 
3 5 100 1
5 5 1 1
...

output

#1 145
#2 5507
#3 9709
#4 2669
#5 3684
#6 774
#7 4797
#8 8786
#9 1374
#10 5040

Solution & Inpression

Code

언어 : JAVA

메모리 : 101,800 kb

실행시간 : 462 ms

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class Solution {

    static class Virus implements Comparable<Virus> {
        int num;
        int i, j;
        int cnt;
        int dir;

        Virus(int num, int i, int j, int cnt, int dir) {
            this.num = num;
            this.i = i;
            this.j = j;
            this.cnt = cnt;
            this.dir = dir;
        }

        @Override
        public int compareTo(Virus o) {
            if (this.num == o.num) {
                return o.cnt - this.cnt;
            }
            return this.num - o.num;
        }

    }

    // (상: 1, 하: 2, 좌: 3, 우: 4)
    static int di[] = { 0, -1, 1, 0, 0 };
    static int dj[] = { 0, 0, 0, -1, 1 };

    static int N;
    static int M;
    static int K;

    static List<Virus> list;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int TC = Integer.parseInt(sc.nextLine());

        for (int tc = 1; tc <= TC; tc++) {
            // 셀의 개수 N, 격리 시간 M, 미생물 군집의 개수 K
            N = sc.nextInt(); // (5 ≤ N ≤ 100)
            M = sc.nextInt(); // (1 ≤ M ≤ 1,000)
            K = sc.nextInt(); // (5 ≤ K ≤ 1,000)

            list = new ArrayList<>();
            for (int k = 0; k < K; k++) {
                int i = sc.nextInt();
                int j = sc.nextInt();
                int cnt = sc.nextInt();
                int dir = sc.nextInt();
                list.add(new Virus(i * N + j, i, j, cnt, dir));
            }

            for (int time = 0; time < M; time++) {
                // 이동
                for (int idx = 0; idx < list.size(); idx++) {
                    Virus virus = list.get(idx);
                    virus.i = virus.i + di[virus.dir];
                    virus.j = virus.j + dj[virus.dir];
                    virus.num = (virus.i * N) + virus.j;

                    // 약품: 미생물 군집이 이동 후 약품이 칠해진 셀에 도착하면 군집 내 미생물의 절반이 죽고, 이동방향이 반대로 바뀐다.
                    if (virus.i == 0 || virus.j == 0 || virus.i == N - 1 || virus.j == N - 1) {
                        virus.cnt /= 2;
                        virus.dir = changeDir(virus.dir);
                        if (virus.cnt == 0) {
                            list.remove(idx);
                            idx--;
                        }
                    }
                }

                Collections.sort(list);

                // 이동 후 두 개 이상의 군집이 한 셀에 모이는 경우 군집들이 합쳐지게 된다.
                for (int idx = 0; idx < list.size() - 1; idx++) {
                    Virus now = list.get(idx);
                    Virus next = list.get(idx + 1);

                    if (now.num == next.num) {
                        now.cnt += next.cnt;
                        list.remove(idx + 1);
                        idx--;
                    }
                }

            }

            int total = 0;
            for (int i = 0; i < list.size(); i++) {
                total += list.get(i).cnt;
            }
            System.out.println("#" + tc + " " + total);

        }
    }

    static int changeDir(int dir) {
        switch (dir) {
        case 1:
            return 2;
        case 2:
            return 1;
        case 3:
            return 4;
        case 4:
            return 3;
        }
        return -1;
    }

}

[BOJ] 1208. 부분수열의 합 2 - (Java)

Problem

제출일 : 2020-03-16

문제 풀이 시간 : 5H

난이도 : ★★★★★


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

N개의 정수로 이루어진 수열이 있을 때, 크기가 양수인 부분수열 중에서 그 수열의 원소를 다 더한 값이 S가 되는 경우의 수를 구하는 프로그램을 작성하시오.

Input

첫째 줄에 정수의 개수를 나타내는 N과 정수 S가 주어진다. (1 ≤ N ≤ 40, |S| ≤ 1,000,000) 둘째 줄에 N개의 정수가 빈 칸을 사이에 두고 주어진다. 주어지는 정수의 절댓값은 100,000을 넘지 않는다.

Output

첫째 줄에 합이 S가 되는 부분수열의 개수를 출력한다.

Example

input

5 0
-7 -3 -2 5 8

output

1

Solution & Inpression

1182. 부분수열의 합문제에서 N이 40까지 커진문제...

여러 블로그를 보며 문제에 해답을 얻고 문제를 해결했다.


문제를 해결한 아이디어는 주어진 입력을 절반씩 나누어 A, B로 구분을 했을때 A와 B는 최대 20개의 숫자를 갖는다. A와 B의 모든 부분수열의 합을 List에 저장을 하였다. (N=20은 주어진 시간내에 완전 탐색이 가능하다.)

부분 수열의 합이 S가 되는 경우는

  1. A List의 값이 S 인 경우
  2. B List의 값이 S 인 경우
  3. A List의 값이 x라 할 때 B List 값이 S-x 인 경우

로 총 3가지의 경우 합이다.

C에 algorithm 라이브러리에 포함된 함수인 lower_bound와 upper_bound를 구현해 문제를 해결했다.

따로 위 함수들은 정리하겠다.

long ans: 모든 경우의 수는 $2^{40}$으로 int형을 사용하면 범위가 초과하니 주의 해야한다.

Code

언어 : JAVA

메모리 : 83140 kb

실행 시간 : 1360 ms

import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

public class Main {
    static int N, S;
    static int ans; // int :2^32 long: 2^64 >> 문제: 2^40
    static int[] input;
    static ArrayList<Integer> L, R;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt();
        S = sc.nextInt();
        input = new int[N];
        for (int i = 0; i < N; i++) {
            input[i] = sc.nextInt();
        }

        L = new ArrayList<>();
        R = new ArrayList<>();
        solve(0, N / 2, 0, L);
        solve(N / 2, N, 0, R);

        Collections.sort(R);

        ans = 0;
        for (int val : L) {
            val = S - val;
            int hi = upper_bound(R, val);
            int lo = lower_bound(R, val);
            ans += hi - lo;
        }

        // 공집합 제거
        if (S == 0) {
            --ans;
        }
        System.out.println(ans);
    }

    // lower bound는 찾고자 하는 값 이상이 처음 나타나는 위치
    private static int lower_bound(ArrayList<Integer> list, int val) {
        int start = 0;
        int end = list.size();
        int mid;
        while (start < end) {
            mid = (start + end) >> 1;
            if (list.get(mid) >= val) {
                end = mid;
            } else {
                start = mid + 1;
            }
        }
        return start;
    }

    // upper bound는 찾고자 하는 값보다 큰 값이 처음으로 나타나는 위치
    private static int upper_bound(ArrayList<Integer> list, int val) {
        int start = 0;
        int end = list.size();
        int mid;
        while (start < end) {
            mid = (start + end) >> 1;
            if (list.get(mid) <= val) {
                start = mid + 1;
            } else {
                end = mid;
            }
        }
        return start;
    }

    // 범위에 해당하는 모든 부분수열의 합을 저장.
    private static void solve(int pos, int end, int sum, ArrayList<Integer> list) {
        if (pos == end) {
            list.add(sum);
            return;
        }
        solve(pos + 1, end, sum, list);
        solve(pos + 1, end, sum + input[pos], list);
    }

}

Reference

http://joonas-yoon.blogspot.com/2016/03/1208-2.html

https://23dotory.tistory.com/27

'Problem > BOJ' 카테고리의 다른 글

[BOJ] 7453. 합이 0인 네 정수 - (Java)  (0) 2020.03.17
[BOJ] 2143. 두 배열의 합 - (Java)  (0) 2020.03.17
[BOJ] 1806. 부분합 - (Java)  (0) 2020.03.15
[BOJ] 2003. 수들의 합 2 - (Java)  (2) 2020.03.15
[BOJ] 12100. 2048 (Easy) - (Java)  (0) 2020.03.15

[SWEA] 5684. [Professional] 운동 (D4) - (Java)

Problem

제출일 : 2020-03-05

문제 풀이 시간 : 30M

난이도 : ★☆


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

N개의 건물과 M개의 도로로 구성되어 있는 마을이 있다.

도로는 건물과 건물 사이에 놓여 있으며, 일방 통행 도로이다.

건물의 번호는 1번부터 N번까지 각각 주어져있다.

원철이는 마을의 도로를 따라 운동을 하기 위한 경로를 찾으려고 한다.

운동을 한 후에는 다시 시작점으로 돌아오는 것이 편하기 때문에, 원철이는 사이클을 찾기를 원한다.

단, 운동을 하는 도중 무슨 일이 생길지 모르므로, 사이클을 이루는 도로의 길이의 합이 최소가 되도록 찾으려고 한다.

도로의 정보가 주어졌을 때, 도로의 길이의 합이 가장 작은 사이클을 찾는 프로그램을 작성하시오.

두 마을을 왕복하는 경우도 사이클에 포함됨에 주의하시오.

Input

첫 줄에 테스트케이스의 개수 T가 주어진다. (1 ≤ T ≤ 20)

각 테스트 케이스의 첫 번째 줄에 건물의 수 N과 도로의 수 M이 주어진다. (2 ≤ N ≤ 400, 2 ≤ M ≤ N*(N-1))

각 테스트 케이스의 두 번째 줄부터 M개의 줄에 걸쳐 도로의 정보 s, e, c가 주어진다.

이는 s번 건물으로부터 e번 건물으로 이동하는 길이 c의 일방 통행 도로가 있다는 의미이다.

거리는 10,000 이하의 자연수이고, 같은 시작점과 도착점을 가진 간선은 최대 한 개 등장한다.

Output

각 테스트케이스마다 한 줄에 걸쳐, 테스트케이스 수 “#(TC) “를 출력하고, 도로의 길이의 합이 가장 작은 사이클의 길이를 출력한다. 만약, 그러한 사이클을 찾을 수 없다면 -1을 출력한다.

Example

input

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

output

#1 3

Solution & Inpression

방향그래프의 BFS, 도착지점으로 돌아오는 사이클을 찾고 사이클을 도는 최소비용을 구하는 문제

Code

언어 : JAVA

메모리 : 96,024 kb

실행시간 : 381 ms

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

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

    public static void main(String args[]) throws Exception {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();// (1 ≤ T ≤ 20)
        for (int tc = 1; tc <= T; tc++) {
            // (2 ≤ N ≤ 400, 2 ≤ M ≤ N*(N-1))
            N = sc.nextInt();
            M = sc.nextInt();
            map = new int[N][N];
            for (int i = 0; i < M; i++) {
                map[sc.nextInt() - 1][sc.nextInt() - 1] = sc.nextInt(); // 거리는 10,000 이하의 자연수
            }
            ans = Integer.MAX_VALUE;
            for (int i = 0; i < N; i++) {
                BFS(i);
            }
            System.out.println("#" + tc + " " + (ans == Integer.MAX_VALUE ? -1 : ans));
        }
    }// end of main

    private static void BFS(int start) {
        boolean visit[] = new boolean[N];
        Queue<int[]> q = new LinkedList<>();
        q.offer(new int[] { start, 0 });
        while (!q.isEmpty()) {
            int[] cur = q.poll();
            for (int i = 0; i < N; i++) {
                if (map[cur[0]][i] != 0 && !visit[i]) {
                    visit[i] = true;
                    q.offer(new int[] { i, cur[1] + map[cur[0]][i] });
                }
                if (map[cur[0]][i] != 0 && i == start) {
                    if (ans > cur[1] + map[cur[0]][i])
                        ans = cur[1] + map[cur[0]][i];
                    return;
                }
            }
        }
    }

}

[BOJ] 1806. 부분합 - (Java)

Problem

제출일 : 2020-03-15

문제 풀이 시간 : 20M

난이도 : ★★


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

10,000 이하의 자연수로 이루어진 길이 N짜리 수열이 주어진다. 이 수열에서 연속된 수들의 부분합 중에 그 합이 S 이상이 되는 것 중, 가장 짧은 것의 길이를 구하는 프로그램을 작성하시오.

Input

첫째 줄에 N (10 ≤ N < 100,000)과 S (0 < S ≤ 100,000,000)가 주어진다. 둘째 줄에는 수열이 주어진다. 수열의 각 원소는 공백으로 구분되어져 있으며, 10,000이하의 자연수이다.

Output

첫째 줄에 구하고자 하는 최소의 길이를 출력한다. 만일 그러한 합을 만드는 것이 불가능하다면 0을 출력하면 된다.

Example

input

4 2
1 1 1 1

output

3

Solution & Inpression

2003. 수들의 합 2응용 문제

주의 해야 할 점은 부분합 중에 그 합이 S 이상이 되는 것으로 정확히 일치하는 것이 아닌 이상이 되는 값을 찾아야 함!!!!

Code

언어 : JAVA

메모리 : 90640 kb

실행 시간 : 684 ms

import java.util.Scanner;

public class Main {

    static int N, S, ans, sum;
    static int[] data;
    static int s, e;

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

        N = sc.nextInt();
        S = sc.nextInt();

        data = new int[N + 1];
        for (int i = 0; i < N; i++) {
            data[i] = sc.nextInt();
        }

        ans = Integer.MAX_VALUE;
        s = 0;
        e = 0;
        while (e <= N && s <= N) {
            if (sum >= S && ans > e - s)
                ans = e - s;

            if (sum < S)
                sum += data[e++];
            else
                sum -= data[s++];
        }
        System.out.println(ans == Integer.MAX_VALUE ? 0 : ans);
    }
}

[BOJ] 2003. 수들의 합 2 - (Java)

Problem

제출일 : 2020-03-15

문제 풀이 시간 : 20M

난이도 : ★


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

N개의 수로 된 수열 A[1], A[2], …, A[N] 이 있다. 이 수열의 i번째 수부터 j번째 수까지의 합 A[i]+A[i+1]+…+A[j-1]+A[j]가 M이 되는 경우의 수를 구하는 프로그램을 작성하시오.

Input

첫째 줄에 N(1≤N≤10,000), M(1≤M≤300,000,000)이 주어진다. 다음 줄에는 A[1], A[2], …, A[N]이 공백으로 분리되어 주어진다. 각각의 A[x]는 30,000을 넘지 않는 자연수이다.

Output

첫째 줄에 경우의 수를 출력한다.

Example

input

4 2
1 1 1 1

output

3

Solution & Inpression

완전탐색? 포인터의 활용..

code 1 : 처음 문제를 풀 때 한번에 더할 개수를 정하고 그 수만큼 더한 모든 경우의 수를 탐색했다.

code 2 : code 1의 최적화 버전으로 양쪽에 포인터를 두어 목표 값보다 작다면 값을 더하고 작으면 값을 빼는 방법으로 굳이 검사하지 않아도 되는 부분을 검사하지 않을 수 있어 속도가 더욱 빠른 코드이다.

Code 1

언어 : JAVA

메모리 : 26960 kb

실행 시간 : 440 ms

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static int N, M, ans;
    static int[] data;

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

        N = sc.nextInt();
        M = sc.nextInt();

        data = new int[N];
        for (int i = 0; i < N; i++) {
            data[i] = sc.nextInt();
        }

        ans = 0;
        for (int i = 1; i <= N; i++) {
            solve(i);
        }
        System.out.println(ans);
    }

    private static void solve(int size) {
        int sum = 0;
        for (int i = 0; i < size; i++) {
            sum += data[i];
        }

        for (int i = size; i < N; i++) {
            if (sum == M)
                ans++;
            sum += data[i];
            sum -= data[i - size];
        }
        if (sum == M)
            ans++;
    }
}

Code 2

언어 : JAVA

메모리 : 27320 kb

실행 시간 : 244 ms

import java.util.Scanner;

public class Main {

    static int N, M, ans, sum;
    static int[] data;
    static int s, e;

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

        N = sc.nextInt();
        M = sc.nextInt();

        data = new int[N + 1];
        for (int i = 0; i < N; i++) {
            data[i] = sc.nextInt();
        }
        s = 0;
        e = 0;

        while (e <= N) {
            if (sum == M)
                ans++;

            if (sum < M)
                sum += data[e++];
            else
                sum -= data[s++];
        }

        System.out.println(ans);

    }

}

'Problem > BOJ' 카테고리의 다른 글

[BOJ] 1208. 부분수열의 합 2 - (Java)  (0) 2020.03.16
[BOJ] 1806. 부분합 - (Java)  (0) 2020.03.15
[BOJ] 12100. 2048 (Easy) - (Java)  (0) 2020.03.15
[BOJ] 13460. 구슬 탈출 2 (Java)  (1) 2020.03.14
[BOJ] 1062. 가르침 (Java)  (0) 2020.03.14

[BOJ] 12100. 2048 (Easy) - (Java)

Problem

제출일 : 2020-03-15

문제 풀이 시간 : 1H

난이도 : ★★★


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

2048 게임은 4×4 크기의 보드에서 혼자 즐기는 재미있는 게임이다. 이 링크를 누르면 게임을 해볼 수 있다.

이 게임에서 한 번의 이동은 보드 위에 있는 전체 블록을 상하좌우 네 방향 중 하나로 이동시키는 것이다. 이때, 같은 값을 갖는 두 블록이 충돌하면 두 블록은 하나로 합쳐지게 된다. 한 번의 이동에서 이미 합쳐진 블록은 또 다른 블록과 다시 합쳐질 수 없다. (실제 게임에서는 이동을 한 번 할 때마다 블록이 추가되지만, 이 문제에서 블록이 추가되는 경우는 없다)

img img img
<그림 1> <그림 2> <그림 3>

<그림 1>의 경우에서 위로 블록을 이동시키면 <그림 2>의 상태가 된다. 여기서, 왼쪽으로 블록을 이동시키면 <그림 3>의 상태가 된다.

img img img img
<그림 4> <그림 5> <그림 6> <그림 7>

<그림 4>의 상태에서 블록을 오른쪽으로 이동시키면 <그림 5>가 되고, 여기서 다시 위로 블록을 이동시키면 <그림 6>이 된다. 여기서 오른쪽으로 블록을 이동시켜 <그림 7>을 만들 수 있다.

img img
<그림 8> <그림 9>

<그림 8>의 상태에서 왼쪽으로 블록을 옮기면 어떻게 될까? 2가 충돌하기 때문에, 4로 합쳐지게 되고 <그림 9>의 상태가 된다.

img img img img
<그림 10> <그림 11> <그림 12> <그림 13>

<그림 10>에서 위로 블록을 이동시키면 <그림 11>의 상태가 된다.

<그림 12>의 경우에 위로 블록을 이동시키면 <그림 13>의 상태가 되는데, 그 이유는 한 번의 이동에서 이미 합쳐진 블록은 또 합쳐질 수 없기 때문이다.

img img
<그림 14> <그림 15>

마지막으로, 똑같은 수가 세 개가 있는 경우에는 이동하려고 하는 쪽의 칸이 먼저 합쳐진다. 예를 들어, 위로 이동시키는 경우에는 위쪽에 있는 블록이 먼저 합쳐지게 된다. <그림 14>의 경우에 위로 이동하면 <그림 15>를 만든다.

이 문제에서 다루는 2048 게임은 보드의 크기가 N×N 이다. 보드의 크기와 보드판의 블록 상태가 주어졌을 때, 최대 5번 이동해서 만들 수 있는 가장 큰 블록의 값을 구하는 프로그램을 작성하시오.

Input

첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2보다 크거나 같고, 1024보다 작거나 같은 2의 제곱꼴이다. 블록은 적어도 하나 주어진다.

Output

최대 5번 이동시켜서 얻을 수 있는 가장 큰 블록을 출력한다.

Example

input

3
2 2 2
4 4 4
8 8 8

output

16

Solution & Inpression

시뮬레이션 + 완전 탐색

모든 경우의 수를 탐색하는 완전 탐색 시뮬레이션 문제

인덱스 조작이 까다로워 ArrayList를 사용하여 문제를 해결하였다. 움직이는 방향에 따라 list에 값을 넣는 순서를 바꾸어 작업했다.

list[i].get(0).equals(list[i].get(1))이렇게 두 수가 합쳐질 수 있는지 검사하였는데 처음 코드는

list[i].get(0)==list[i].get(1)이었는데 왜 인지 모르게 틀렸다. 왜 지?????

Code

언어 : JAVA

메모리 : 44464 kb

실행 시간 : 736 ms

import java.util.ArrayList;
import java.util.Scanner;

public class Main {
    static int N, ans;
    static int[][] map;
    static int[] data;
    static ArrayList<Integer>[] list;

    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();
            }
        }
        ans = 0;
        data = new int[5];

        list = new ArrayList[N];
        for (int i = 0; i < N; i++) {
            list[i] = new ArrayList<>();
        }
        solve(0, 0);
        System.out.println(ans);

    }

    private static void solve(int index, int depth) {
        if (depth == 5) {
            check();
            return;
        }
        for (int i = 0; i < 4; i++) {
            if (data[depth] == 0) {
                data[depth] = i;
                solve(index, depth + 1);
                data[depth] = 0;
            }
        }
    }

    private static void check() {
        int[][] copy = new int[N][N];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                copy[i][j] = map[i][j];
            }
        }

        for (int time = 0; time < 5; time++) {
            switch (data[time]) {
            case 0: // 상
                for (int r = 0; r < N; r++) {
                    for (int c = 0; c < N; c++) {
                        if (copy[r][c] != 0) {
                            list[c].add(copy[r][c]);
                            copy[r][c] = 0;
                        }
                    }
                }
                for (int i = 0; i < N; i++) {
                    int r = 0;
                    while (list[i].size() != 0) {
                        if (list[i].size() != 1 && list[i].get(0).equals(list[i].get(1))) {
                            copy[r++][i] = list[i].get(0) << 1;
                            list[i].remove(0);
                            list[i].remove(0);
                        } else {
                            copy[r++][i] = list[i].get(0);
                            list[i].remove(0);
                        }
                    }
                }
                break;

            case 1: // 하
                for (int r = N - 1; r >= 0; r--) {
                    for (int c = 0; c < N; c++) {
                        if (copy[r][c] != 0) {
                            list[c].add(copy[r][c]);
                            copy[r][c] = 0;
                        }
                    }
                }

                for (int i = 0; i < N; i++) {
                    int r = N - 1;
                    while (list[i].size() != 0) {
                        if (list[i].size() != 1 && list[i].get(0).equals(list[i].get(1))) {
                            copy[r--][i] = list[i].get(0) << 1;
                            list[i].remove(0);
                            list[i].remove(0);
                        } else {
                            copy[r--][i] = list[i].get(0);
                            list[i].remove(0);
                        }
                    }
                }
                break;
            case 2: // 좌
                for (int r = 0; r < N; r++) {
                    for (int c = 0; c < N; c++) {
                        if (copy[r][c] != 0) {
                            list[r].add(copy[r][c]);
                            copy[r][c] = 0;
                        }
                    }
                }

                for (int i = 0; i < N; i++) {
                    int c = 0;
                    while (list[i].size() != 0) {
                        if (list[i].size() != 1 && list[i].get(0).equals(list[i].get(1))) {
                            copy[i][c++] = list[i].get(0) << 1;
                            list[i].remove(0);
                            list[i].remove(0);
                        } else {
                            copy[i][c++] = list[i].get(0);
                            list[i].remove(0);
                        }
                    }
                }
                break;
            case 3: // 우
                for (int r = 0; r < N; r++) {
                    for (int c = N - 1; c >= 0; c--) {
                        if (copy[r][c] != 0) {
                            list[r].add(copy[r][c]);
                            copy[r][c] = 0;
                        }
                    }
                }

                for (int i = 0; i < N; i++) {
                    int c = N - 1;
                    while (list[i].size() != 0) {
                        if (list[i].size() != 1 && list[i].get(0).equals(list[i].get(1))) {
                            copy[i][c--] = list[i].get(0) << 1;
                            list[i].remove(0);
                            list[i].remove(0);
                        } else {
                            copy[i][c--] = list[i].get(0);
                            list[i].remove(0);
                        }
                    }
                }
                break;
            }// end of switch
        }

        int max = 0;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (max < copy[i][j])
                    max = copy[i][j];
            }
        }

        if (ans < max)
            ans = max;

    }
}

'Problem > BOJ' 카테고리의 다른 글

[BOJ] 1806. 부분합 - (Java)  (0) 2020.03.15
[BOJ] 2003. 수들의 합 2 - (Java)  (2) 2020.03.15
[BOJ] 13460. 구슬 탈출 2 (Java)  (1) 2020.03.14
[BOJ] 1062. 가르침 (Java)  (0) 2020.03.14
[BOJ] 14391. 종이 조각 (Java)  (0) 2020.03.14

[BOJ] 13460. 구슬 탈출 2 (Java)

Problem

제출일 : 2020-03-14

문제 풀이 시간 : 2H 20M

난이도 : ★★★★☆


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

스타트링크에서 판매하는 어린이용 장난감 중에서 가장 인기가 많은 제품은 구슬 탈출이다. 구슬 탈출은 직사각형 보드에 빨간 구슬과 파란 구슬을 하나씩 넣은 다음, 빨간 구슬을 구멍을 통해 빼내는 게임이다.

보드의 세로 크기는 N, 가로 크기는 M이고, 편의상 1×1크기의 칸으로 나누어져 있다. 가장 바깥 행과 열은 모두 막혀져 있고, 보드에는 구멍이 하나 있다. 빨간 구슬과 파란 구슬의 크기는 보드에서 1×1크기의 칸을 가득 채우는 사이즈이고, 각각 하나씩 들어가 있다. 게임의 목표는 빨간 구슬을 구멍을 통해서 빼내는 것이다. 이때, 파란 구슬이 구멍에 들어가면 안 된다.

이때, 구슬을 손으로 건드릴 수는 없고, 중력을 이용해서 이리 저리 굴려야 한다. 왼쪽으로 기울이기, 오른쪽으로 기울이기, 위쪽으로 기울이기, 아래쪽으로 기울이기와 같은 네 가지 동작이 가능하다.

각각의 동작에서 공은 동시에 움직인다. 빨간 구슬이 구멍에 빠지면 성공이지만, 파란 구슬이 구멍에 빠지면 실패이다. 빨간 구슬과 파란 구슬이 동시에 구멍에 빠져도 실패이다. 빨간 구슬과 파란 구슬은 동시에 같은 칸에 있을 수 없다. 또, 빨간 구슬과 파란 구슬의 크기는 한 칸을 모두 차지한다. 기울이는 동작을 그만하는 것은 더 이상 구슬이 움직이지 않을 때 까지이다.

보드의 상태가 주어졌을 때, 최소 몇 번 만에 빨간 구슬을 구멍을 통해 빼낼 수 있는지 구하는 프로그램을 작성하시오.

Input

첫 번째 줄에는 보드의 세로, 가로 크기를 의미하는 두 정수 N, M (3 ≤ N, M ≤ 10)이 주어진다. 다음 N개의 줄에 보드의 모양을 나타내는 길이 M의 문자열이 주어진다. 이 문자열은 '.', '#', 'O', 'R', 'B' 로 이루어져 있다. '.'은 빈 칸을 의미하고, '#'은 공이 이동할 수 없는 장애물 또는 벽을 의미하며, 'O'는 구멍의 위치를 의미한다. 'R'은 빨간 구슬의 위치, 'B'는 파란 구슬의 위치이다.

입력되는 모든 보드의 가장자리에는 모두 '#'이 있다. 구멍의 개수는 한 개 이며, 빨간 구슬과 파란 구슬은 항상 1개가 주어진다.

Output

최소 몇 번 만에 빨간 구슬을 구멍을 통해 빼낼 수 있는지 출력한다. 만약, 10번 이하로 움직여서 빨간 구슬을 구멍을 통해 빼낼 수 없으면 -1을 출력한다.

Example

input

5 5
#####
#..B#
#.#.#
#RO.#
#####

output

1

Solution & Inpression

시뮬레이션 + 그래프 탐색(BFS)

시뮬레이션 문제도 자주 풀어봐야 하지만 구현의 아이디어가 어렵다 라기보단 구현이 까다로워 자주 풀지 못하는 문제 유형이다. 많은 연습이 필요한 유형이지만 풀기 싫은게 사실이다.

시뮬레이션 문제는 많은 테스트케이스가 주어진다면 쉽게 문제를 찾을지도 모르지만 보통 늪에 빠져버린다.

보통 최소값을 찾는 탐색 문제는 BFS로 접근을 하며 문제를 해결할 때 배열로 가지고 있으며 해결하는 것을 선호하지만 이번엔 클래스를 이용하여 구현해보았다.

움직이는 과정이 까다롭기 때문에 따로 배열을 만들어 움직여 주었다. 이때 현재 위치와 방향을 매개변수로 넘겨주는데 현재 위치를 클래스로 넘기기에 값을 참조 호출하기 때문에 값을 복사하여 사용해야 했다.

또 방문 처리를 어떻게 할까 고민해보았는데 아래 소스와 같이 4차원 배열을 이용하여 해결했다.

최적화를 위해 다른 사람들이 푼 소스를 보았는데 일단 움직이고 같으면 처리하는 방식으로 문제를 해결한 사람도 있었다. 이 방법이 더 깔끔한 방법이지 않을까 싶기도 하다.

Code

언어 : JAVA

메모리 : 15680 kb

실행 시간 : 116 ms

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

public class Main {
    static int N, M;
    static char[][] map;

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

    static boolean[][][][] visit;

    static class Ball {
        int[] Red;
        int[] Blue;
        int move;

        public Ball() {
            this.move = 0;
        }

        public Ball(Ball now) {
            this.Red = now.Red.clone();
            this.Blue = now.Blue.clone();
            this.move = now.move;
        }

    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // N, M (3 ≤ N, M ≤ 10)
        N = sc.nextInt();
        M = sc.nextInt();

        // '.'은 빈 칸
        // '#'은 벽
        // 'O'는 구멍의 위치.
        // 'R'은 빨간 구슬의 위치
        // 'B'는 파란 구슬의 위치

        map = new char[N][M];
        Ball b = new Ball();
        for (int i = 0; i < N; i++) {
            String str = sc.next();
            for (int j = 0; j < M; j++) {
                map[i][j] = str.charAt(j);
                if (map[i][j] == 'R') {
                    b.Red = new int[] { i, j };
                    map[i][j] = '.';
                } else if (map[i][j] == 'B') {
                    b.Blue = new int[] { i, j };
                    map[i][j] = '.';
                }
            }
        }

        visit = new boolean[N][M][N][M];
        Queue<Ball> q = new LinkedList<>();
        q.offer(b);
        visit[b.Red[0]][b.Red[1]][b.Blue[0]][b.Blue[1]] = true;
        while (!q.isEmpty()) {
            Ball now = q.poll();
            // 실패
            if (map[now.Blue[0]][now.Blue[1]] == 'O') {
                // System.out.println("공빠져 실패");
                continue;
            } else if (now.move == 11) {
                // System.out.println("시간 초과 실패");
                continue;
            }

            // 성공!
            if (map[now.Red[0]][now.Red[1]] == 'O') {
                System.out.println(now.move);
                System.exit(0);
            }

            for (int dir = 0; dir < 4; dir++) {
                Ball next = move(dir, now);
                if (visit[next.Red[0]][next.Red[1]][next.Blue[0]][next.Blue[1]])
                    continue;
                visit[next.Red[0]][next.Red[1]][next.Blue[0]][next.Blue[1]] = true;
                q.offer(next);
            }
        }

        System.out.println("-1");
    }

    private static Ball move(int dir, Ball now) {
        Ball next = new Ball(now);
        int nr = 0, nc = 0, flag = 0;
        switch (dir) {
        case 0: // 상
            if (next.Red[0] > next.Blue[0]) // 파란공이 더 위에 있으면
                // 위에 있는 파란 공 먼저 움직임.(0)
                flag = 0;
            else
                flag = 1;

            break;
        case 1: // 하
            if (next.Red[0] > next.Blue[0]) // 빨간공이 더 아래 있으면
                // 아래에 있는 빨간 공 먼저 움직임.(1)
                flag = 1;
            else
                flag = 0;

            break;
        case 2: // 좌
            if (next.Red[1] > next.Blue[1]) // 파란공이 더 왼쪽 있으면
                // 왼쪽에 있는 파란 공 먼저 움직임.(0)
                flag = 0;
            else
                flag = 1;
            break;
        case 3: // 우
            if (next.Red[1] > next.Blue[1]) // 빨간공이 더 오른쪽 있으면
                // 오른쪽에 있는 빨간 공 먼저 움직임.(1)
                flag = 1;
            else
                flag = 0;

            break;
        }// end of switch

        // 파란공 먼저 움직이는 경우
        if (flag == 0) {
            nr = next.Blue[0] + dr[dir];
            nc = next.Blue[1] + dc[dir];
            while (map[nr][nc] == '.') {
                nr += dr[dir];
                nc += dc[dir];
            }
            if (map[nr][nc] == 'O') {
                next.Blue = new int[] { nr, nc };
            } else
                next.Blue = new int[] { nr - dr[dir], nc - dc[dir] };

            nr = next.Red[0] + dr[dir];
            nc = next.Red[1] + dc[dir];
            while (map[nr][nc] == '.') {
                if (nr == next.Blue[0] && nc == next.Blue[1])
                    break;
                nr += dr[dir];
                nc += dc[dir];
            }
            if (map[nr][nc] == 'O') {
                next.Red = new int[] { nr, nc };
            } else
                next.Red = new int[] { nr - dr[dir], nc - dc[dir] };
        }
        // 빨간공 먼저 움직이는 경우
        else if (flag == 1) {
            nr = next.Red[0] + dr[dir];
            nc = next.Red[1] + dc[dir];
            while (map[nr][nc] == '.') {
                nr += dr[dir];
                nc += dc[dir];
            }
            if (map[nr][nc] == 'O') {
                next.Red = new int[] { nr, nc };
            } else
                next.Red = new int[] { nr - dr[dir], nc - dc[dir] };

            nr = next.Blue[0] + dr[dir];
            nc = next.Blue[1] + dc[dir];
            while (map[nr][nc] == '.') {
                if (nr == next.Red[0] && nc == next.Red[1])
                    break;
                nr += dr[dir];
                nc += dc[dir];
            }
            if (map[nr][nc] == 'O') {
                next.Blue = new int[] { nr, nc };
            } else
                next.Blue = new int[] { nr - dr[dir], nc - dc[dir] };
        }

        next.move++;
        return next;
    }

}

'Problem > BOJ' 카테고리의 다른 글

[BOJ] 2003. 수들의 합 2 - (Java)  (2) 2020.03.15
[BOJ] 12100. 2048 (Easy) - (Java)  (0) 2020.03.15
[BOJ] 1062. 가르침 (Java)  (0) 2020.03.14
[BOJ] 14391. 종이 조각 (Java)  (0) 2020.03.14
[BOJ] 2580. 스도쿠 (Java)  (0) 2020.03.13

[BOJ] 1062. 가르침 (Java)

Problem

제출일 : 2020-03-14

문제 풀이 시간 : 1H 30M

난이도 : ★★★


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

남극에 사는 김지민 선생님은 학생들이 되도록이면 많은 단어를 읽을 수 있도록 하려고 한다. 그러나 지구온난화로 인해 얼음이 녹아서 곧 학교가 무너지기 때문에, 김지민은 K개의 글자를 가르칠 시간 밖에 없다. 김지민이 가르치고 난 후에는, 학생들은 그 K개의 글자로만 이루어진 단어만을 읽을 수 있다. 김지민은 어떤 K개의 글자를 가르쳐야 학생들이 읽을 수 있는 단어의 개수가 최대가 되는지 고민에 빠졌다.

남극언어의 모든 단어는 "anta"로 시작되고, "tica"로 끝난다. 남극언어에 단어는 N개 밖에 없다고 가정한다. 학생들이 읽을 수 있는 단어의 최댓값을 구하는 프로그램을 작성하시오.

Input

첫째 줄에 단어의 개수 N과 K가 주어진다. N은 50보다 작거나 같은 자연수이고, K는 26보다 작거나 같은 자연수 또는 0이다. 둘째 줄부터 N개의 줄에 남극 언어의 단어가 주어진다. 단어는 영어 소문자로만 이루어져 있고, 길이가 8보다 크거나 같고, 15보다 작거나 같다. 모든 단어는 중복되지 않는다.

Output

첫째 줄에 김지민이 K개의 글자를 가르칠 때, 학생들이 읽을 수 있는 단어 개수의 최댓값을 출력한다.

Example

input

3 6
antarctica
antahellotica
antacartica

output

2

Solution & Inpression

비트마스킹 + 완전탐색 문제

입력 받은 단어를 분석해 어떤 알파벳이 사용되는지 확인하고 모든 조합을 고려하여 결과를 출력하였다.

모든 단어는 "anta"로 시작되고, "tica"는 부분의 처리와 boolean배열 대신 int를 이용한 비트마스킹을 사용했다면 더 빠른 결과를 출력할 수 있을 것 같다.

Code

언어 : JAVA

메모리 : 15284 kb

실행 시간 : 2140 ms

import java.util.Scanner;

public class Main {
    static int N, K, ans;
    static boolean[][] word;
    static boolean[] visit;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt(); // N은 50보다 작거나 같은 자연수
        K = sc.nextInt(); // K는 26보다 작거나 같은 자연수 또는 0

        // N개의 줄에 남극 언어의 단어
        word = new boolean[N][26];
        for (int i = 0; i < N; i++) {
            String tmp = sc.next();
            for (int j = 0; j < tmp.length(); j++) {
                word[i][tmp.charAt(j) - 'a'] = true;
            }
        }
        visit = new boolean[26];
        ans = 0;
        solve(0, 0);
        System.out.println(ans);
    }

    private static void solve(int index, int depth) {
        if (depth == K) {
            check();
            return;
        }
        for (int i = index; i < 26; i++) {
            if (!visit[i]) {
                visit[i] = true;
                solve(i, depth + 1);
                visit[i] = false;
            }
        }

    }

    private static void check() {
        int ret = 0;

        boolean flag = true;
        for (int k = 0; k < N; k++) {
            flag = true;
            for (int i = 0; i < 26; i++) {
                if (word[k][i] && !visit[i]) {
                    flag = false;
                    break;
                }
            }
            if (flag)
                ret++;
        }

        ans = Math.max(ret, ans);
    }
}

'Problem > BOJ' 카테고리의 다른 글

[BOJ] 12100. 2048 (Easy) - (Java)  (0) 2020.03.15
[BOJ] 13460. 구슬 탈출 2 (Java)  (1) 2020.03.14
[BOJ] 14391. 종이 조각 (Java)  (0) 2020.03.14
[BOJ] 2580. 스도쿠 (Java)  (0) 2020.03.13
[BOJ] 9663. N-Queen (Java)  (1) 2020.03.13