www.acmicpc.net/problem/3190

 

3190번: 뱀

 'Dummy' 라는 도스게임이 있다. 이 게임에는 뱀이 나와서 기어다니는데, 사과를 먹으면 뱀 길이가 늘어난다. 뱀이 이리저리 기어다니다가 벽 또는 자기자신의 몸과 부딪히면 게임이 끝난다. 게임

www.acmicpc.net

문제

 'Dummy' 라는 도스게임이 있다. 이 게임에는 뱀이 나와서 기어다니는데, 사과를 먹으면 뱀 길이가 늘어난다. 뱀이 이리저리 기어다니다가 벽 또는 자기자신의 몸과 부딪히면 게임이 끝난다.

게임은 NxN 정사각 보드위에서 진행되고, 몇몇 칸에는 사과가 놓여져 있다. 보드의 상하좌우 끝에 벽이 있다. 게임이 시작할때 뱀은 맨위 맨좌측에 위치하고 뱀의 길이는 1 이다. 뱀은 처음에 오른쪽을 향한다.

뱀은 매 초마다 이동을 하는데 다음과 같은 규칙을 따른다.

  • 먼저 뱀은 몸길이를 늘려 머리를 다음칸에 위치시킨다.
  • 만약 이동한 칸에 사과가 있다면, 그 칸에 있던 사과가 없어지고 꼬리는 움직이지 않는다.
  • 만약 이동한 칸에 사과가 없다면, 몸길이를 줄여서 꼬리가 위치한 칸을 비워준다. 즉, 몸길이는 변하지 않는다.

사과의 위치와 뱀의 이동경로가 주어질 때 이 게임이 몇 초에 끝나는지 계산하라.

입력

첫째 줄에 보드의 크기 N이 주어진다. (2 ≤ N ≤ 100) 다음 줄에 사과의 개수 K가 주어진다. (0 ≤ K ≤ 100)

다음 K개의 줄에는 사과의 위치가 주어지는데, 첫 번째 정수는 행, 두 번째 정수는 열 위치를 의미한다. 사과의 위치는 모두 다르며, 맨 위 맨 좌측 (1행 1열) 에는 사과가 없다.

다음 줄에는 뱀의 방향 변환 횟수 L 이 주어진다. (1 ≤ L ≤ 100)

다음 L개의 줄에는 뱀의 방향 변환 정보가 주어지는데,  정수 X와 문자 C로 이루어져 있으며. 게임 시작 시간으로부터 X초가 끝난 뒤에 왼쪽(C가 'L') 또는 오른쪽(C가 'D')로 90도 방향을 회전시킨다는 뜻이다. X는 10,000 이하의 양의 정수이며, 방향 전환 정보는 X가 증가하는 순으로 주어진다.

출력

첫째 줄에 게임이 몇 초에 끝나는지 출력한다.

예제 입력 1

6

3

3 4

2 5

5 3

3

3 D

15 L

17 D

예제 출력 1

9

예제 입력 2

10

4

1 2

1 3

1 4

1 5

4

8 D

10 D

11 D

13 L

예제 출력 2

21

예제 입력 3

10

5

1 5

1 3

1 2

1 6

1 7

4

8 D

10 D

11 D

13 L

예제 출력 3

13

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.StringTokenizer;

class Pair {
    int r, c;
    Pair(int r, int c) {
        this.r = r;
        this.c = c;
    }
}

public class Main {
    static int n, k, l, sec;
    static int[][] map;
    static String[][] move;
    static boolean[][] check;  // check[i][j] == (i, j)에 뱀의 몸통이 있는가?
    static Deque<Pair> que = new ArrayDeque<>();

    static int[] rArr = {0, 1, 0, -1};  // 동남서북
    static int[] cArr = {1, 0, -1, 0};

    public static boolean isFinish(int nr, int nc) {
        if(!(-1 < nr && nr < n && -1 < nc && nc < n) || check[nr][nc]) {
            return true;
        }else {
            return false;
        }
    }

    public static void process() {
        int dir = 0;  // 첫 시작은 동쪽
        int moveIdx = 0;
        while(true) {
            sec += 1;

            Pair head = que.getFirst();
            int nr = head.r + rArr[dir];
            int nc = head.c + cArr[dir];

            if(isFinish(nr, nc)) {
                break;
            }

            que.addFirst(new Pair(nr, nc));
            check[nr][nc] = true;
            if(map[nr][nc] != 1) {  // 사과 아닐 경우 꼬리 제거
                Pair tail = que.getLast();
                check[tail.r][tail.c] = false;
                que.removeLast();
            }else {
                map[nr][nc] = 0;
            }

            // 아직 수행할 명령 남아있고 시간이 일치한다면 수행
            if(moveIdx < l && sec == Integer.parseInt(move[moveIdx][0])) {
                String str = move[moveIdx][1];
                moveIdx += 1;
                if(str.equals("D")) {
                    dir += 1;
                    if(dir == 4) dir = 0;
                }else {
                    dir -= 1;
                    if(dir == -1) dir = 3;
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        k = Integer.parseInt(br.readLine());
        map = new int[n][n];  // map[i][j] == 1 : 사과
        check = new boolean[n][n];

        check[0][0] = true;
        que.addLast(new Pair(0, 0));
        for(int i = 0; i < k; i++) {
            st = new StringTokenizer(br.readLine());
            int r = Integer.parseInt(st.nextToken()) - 1;
            int c = Integer.parseInt(st.nextToken()) - 1;
            map[r][c] = 1;
        }

        l = Integer.parseInt(br.readLine());
        move =  new String[l][2];
        for(int i = 0; i < l; i++) {
            st = new StringTokenizer(br.readLine());
            move[i][0] = st.nextToken();
            move[i][1] = st.nextToken();
        }

        process();
        System.out.println(sec);
    }
}

 

삼성 기출 구현 문제.

 

구현 문제들을 풀면서 느끼는 것은 문제를 정말 꼼꼼히 읽어야 한다는 것이다. 

사소한 부분이라도 놓치면 어긋나버리기 십상이다.

 

뱀의 이동을 나타내기 위해 Deque를 사용했고 자신의 몸통과 만나는지는 boolean[][] check로 해결했다.

 

문제의 조건에서는 0행0열이 아니라 1행1열부터 시작하기 때문에 사과의 위치는 1씩 빼서 입력해야 한다. 

이걸 놓쳐서 잠깐 동안 고생했다.

 

알고리즘 문제를 푸는 동안은 스트레스 조절이 정말 중요한 것 같다.

머리에 열이 한 번 받기 시작하면 걷잡을 수 없어지는 느낌이다.

 

 

 

 

www.acmicpc.net/problem/14502

 

14502번: 연구소

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다. 연구소는 크

www.acmicpc.net

문제

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다.

연구소는 크기가 N×M인 직사각형으로 나타낼 수 있으며, 직사각형은 1×1 크기의 정사각형으로 나누어져 있다. 연구소는 빈 칸, 벽으로 이루어져 있으며, 벽은 칸 하나를 가득 차지한다. 

일부 칸은 바이러스가 존재하며, 이 바이러스는 상하좌우로 인접한 빈 칸으로 모두 퍼져나갈 수 있다. 새로 세울 수 있는 벽의 개수는 3개이며, 꼭 3개를 세워야 한다.

예를 들어, 아래와 같이 연구소가 생긴 경우를 살펴보자.

2 0 0 0 1 1 0

0 0 1 0 1 2 0

0 1 1 0 1 0 0

0 1 0 0 0 0 0

0 0 0 0 0 1 1

0 1 0 0 0 0 0

0 1 0 0 0 0 0

이때, 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 곳이다. 아무런 벽을 세우지 않는다면, 바이러스는 모든 빈 칸으로 퍼져나갈 수 있다.

2행 1열, 1행 2열, 4행 6열에 벽을 세운다면 지도의 모양은 아래와 같아지게 된다.

2 1 0 0 1 1 0

1 0 1 0 1 2 0

0 1 1 0 1 0 0

0 1 0 0 0 1 0

0 0 0 0 0 1 1

0 1 0 0 0 0 0

0 1 0 0 0 0 0

바이러스가 퍼진 뒤의 모습은 아래와 같아진다.

2 1 0 0 1 1 2

1 0 1 0 1 2 2

0 1 1 0 1 2 2

0 1 0 0 0 1 2

0 0 0 0 0 1 1

0 1 0 0 0 0 0

0 1 0 0 0 0 0

벽을 3개 세운 뒤, 바이러스가 퍼질 수 없는 곳을 안전 영역이라고 한다. 위의 지도에서 안전 영역의 크기는 27이다.

연구소의 지도가 주어졌을 때 얻을 수 있는 안전 영역 크기의 최댓값을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. (3 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에 지도의 모양이 주어진다. 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 위치이다. 2의 개수는 2보다 크거나 같고, 10보다 작거나 같은 자연수이다.

빈 칸의 개수는 3개 이상이다.

출력

첫째 줄에 얻을 수 있는 안전 영역의 최대 크기를 출력한다.

예제 입력 1

7 7

2 0 0 0 1 1 0

0 0 1 0 1 2 0

0 1 1 0 1 0 0

0 1 0 0 0 0 0

0 0 0 0 0 1 1

0 1 0 0 0 0 0

0 1 0 0 0 0 0

예제 출력 1

27

예제 입력 2

4 6

0 0 0 0 0 0

1 0 0 0 0 2

1 1 1 0 0 2

0 0 0 0 0 2

예제 출력 2

9

예제 입력 3

8 8

2 0 0 0 0 0 0 2

2 0 0 0 0 0 0 2

2 0 0 0 0 0 0 2

2 0 0 0 0 0 0 2

2 0 0 0 0 0 0 2

0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0

예제 출력 3

3

 

 

 

 

 

 

 

풀이 1. (중복 조합 모두 계산)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;

class Pair {
    int r, c;
    Pair(int r, int c) {
        this.r = r;
        this.c = c;
    }
}

public class Main {
    static int n, m, ans;
    static int[][] map;
    static int[][] temp;
    static int[][] virus;
    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1, 1};

    public static int calculate() {
        int cnt = 0;
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(virus[i][j] == 0)
                    cnt += 1;
            }
        }
        return cnt;
    }

    public static void dfs(int dept) {
        if(dept == 3) {
            bfs();  // 바이러스 퍼뜨리기
            return;
        }

        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(temp[i][j] == 0) {
                    temp[i][j] = 1;
                    dfs(dept + 1);
                    temp[i][j] = 0;
                }
            }
        }
    }

    public static void bfs() {
        // deep copy
        for(int i = 0; i < n; i++) {
            virus[i] = temp[i].clone();
        }

        // 큐 초기화
        Queue<Pair> que = new ArrayDeque<>();
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(virus[i][j] == 2) {
                    que.add(new Pair(i, j));
                }
            }
        }

        while(!que.isEmpty()) {
            Pair p = que.poll();
            for(int i = 0; i < 4; i++) {
                int nr = p.r + rArr[i];
                int nc = p.c + cArr[i];
                if(-1 < nr && nr < n && -1 < nc && nc < m) {
                    if(virus[nr][nc] == 0) {
                        virus[nr][nc] = 2;
                        que.add(new Pair(nr, nc));
                    }
                }
            }
        }

        // 바이러스 확산 완료됐으면 안전 영역 계산
        int safe = calculate();
        ans = Math.max(ans, safe);
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        map = new int[n][m];
        temp = new int[n][m];
        virus = new int[n][m];

        for(int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j < m; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        // 벽 3개 놓기 (백트래킹)
        // 바이러스 퍼트리기 (bfs)
        // 다 퍼뜨린 후 안전 영역 세기

        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(map[i][j] == 0) {
                    // deep copy
                    for(int k = 0; k < n; k++) {
                        temp[k] = map[k].clone();
                    }
                    temp[i][j] = 1;
                    dfs(1);
                    temp[i][j] = 0;
                }
            }
        }
        System.out.println(ans);
    }
}

 

N제한이 크지 않아서 돌아가긴 하지만 효율적이지는 못하다.

 

i, j 이중 포문을 돌며 0인 위치에 대해서 재귀를 들어가는데, 이렇게 하면 중복 조합을 제거하지 못한다.

 

 

 

 

 

풀이 2. (중복 조합 제거)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;

class Pair {
    int r, c;
    Pair(int r, int c) {
        this.r = r;
        this.c = c;
    }
}

public class Main {
    static int n, m, ans;
    static int[][] map;
    static int[][] temp;
    static int[][] virus;

    static int zeroIdx;
    static int[][] zero;
    static boolean[] zeroCheck;

    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1, 1};

    public static int calculate() {
        int cnt = 0;
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(virus[i][j] == 0)
                    cnt += 1;
            }
        }
        return cnt;
    }

    public static void dfs(int dept, int idx) {
        if(dept == 3) {
            bfs();  // 바이러스 퍼뜨리기
            return;
        }

        for(int i = idx + 1; i < zeroIdx; i++) {
            if(!zeroCheck[i]) {
                zeroCheck[i] = true;
                temp[zero[i][0]][zero[i][1]] = 1;
                dfs(dept + 1, i);
                temp[zero[i][0]][zero[i][1]] = 0;
                zeroCheck[i] = false;
            }
        }
    }

    public static void bfs() {
        // deep copy
        for(int i = 0; i < n; i++) {
            virus[i] = temp[i].clone();
        }

        // 큐 초기화
        Queue<Pair> que = new ArrayDeque<>();
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(virus[i][j] == 2) {
                    que.add(new Pair(i, j));
                }
            }
        }

        while(!que.isEmpty()) {
            Pair p = que.poll();
            for(int i = 0; i < 4; i++) {
                int nr = p.r + rArr[i];
                int nc = p.c + cArr[i];
                if(-1 < nr && nr < n && -1 < nc && nc < m) {
                    if(virus[nr][nc] == 0) {
                        virus[nr][nc] = 2;
                        que.add(new Pair(nr, nc));
                    }
                }
            }
        }

        // 바이러스 확산 완료됐으면 안전 영역 계산
        int safe = calculate();
        ans = Math.max(ans, safe);
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        map = new int[n][m];
        temp = new int[n][m];
        virus = new int[n][m];
        zero = new int[n*m][2];
        zeroCheck = new boolean[n*m];

        for(int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j < m; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
                if(map[i][j] == 0) {
                    zero[zeroIdx][0] = i;
                    zero[zeroIdx++][1] = j;
                }
            }
        }

        // 벽 3개 놓기 (백트래킹)
        // 바이러스 퍼트리기 (bfs)
        // 다 퍼뜨린 후 안전 영역 세기

        for(int i = 0; i < zeroIdx; i++) {
            // deep copy
            for(int j = 0; j < n; j++) {
                temp[j] = map[j].clone();
            }

            zeroCheck[i] = true;
            temp[zero[i][0]][zero[i][1]] = 1;
            dfs(1, i);
            temp[zero[i][0]][zero[i][1]] = 0;
            zeroCheck[i] = false;
        }

        System.out.println(ans);
    }
}

 

0인 좌표를 담는 배열을 따로 만들어서 중복 조합을 제거했다.

 

아직도 메모리가 좀 많이 먹는 거 같긴 하지만 이전보다는 훨씬 낫다.

 

 

 

 

www.acmicpc.net/problem/20055

 

20055번: 컨베이어 벨트 위의 로봇

길이가 N인 컨베이어 벨트가 있고, 길이가 2N인 벨트가 이 컨베이어 벨트를 위아래로 감싸며 돌고 있다. 벨트는 길이 1 간격으로 2N개의 칸으로 나뉘어져 있으며, 각 칸에는 아래 그림과 같이 1부

www.acmicpc.net

문제

길이가 N인 컨베이어 벨트가 있고, 길이가 2N인 벨트가 이 컨베이어 벨트를 위아래로 감싸며 돌고 있다. 벨트는 길이 1 간격으로 2N개의 칸으로 나뉘어져 있으며, 각 칸에는 아래 그림과 같이 1부터 2N까지의 번호가 매겨져 있다.

벨트가 한 칸 회전하면 1번부터 2N-1번까지의 칸은 다음 번호의 칸이 있는 위치로 이동하고, 2N번 칸은 1번 칸의 위치로 이동한다. i번 칸의 내구도는 Ai이다. 위의 그림에서 1번 칸이 있는 위치를 "올라가는 위치", N번 칸이 있는 위치를 "내려가는 위치"라고 한다.

컨베이어 벨트에 박스 모양 로봇을 하나씩 올리려고 한다. 로봇은 올라가는 위치에만 땅에서 올라가고, 내려가는 위치에서만 땅으로 내려갈 수 있다. 내려가는 위치에 로봇이 있는 경우 로봇은 반드시 땅으로 내려가야 한다. 로봇이 어떤 칸에 올라가거나 이동하면 그 칸의 내구도는 즉시 1만큼 감소한다. 내구도가 0인 칸에는 로봇이 올라갈 수 없다.

로봇은 컨베이어 벨트 위에서 스스로 이동할 수 있다.

컨베이어 벨트를 이용해 로봇들을 건너편으로 옮기려고 한다. 로봇을 옮기는 과정에서는 아래와 같은 일이 순서대로 일어난다.

  1. 벨트가 한 칸 회전한다.
  2. 가장 먼저 벨트에 올라간 로봇부터, 벨트가 회전하는 방향으로 한 칸 이동할 수 있다면 이동한다. 만약 이동할 수 없다면 가만히 있는다.
    1. 로봇이 이동하기 위해서는 이동하려는 칸에 로봇이 없으며, 그 칸의 내구도가 1 이상 남아 있어야 한다.
  3. 올라가는 위치에 로봇이 없다면 로봇을 하나 올린다.
  4. 내구도가 0인 칸의 개수가 K개 이상이라면 과정을 종료한다. 그렇지 않다면 1번으로 돌아간다.

종료되었을 때 몇 번째 단계가 진행 중이었는지 구해보자. 가장 처음 수행되는 단계는 1번째 단계이다.

입력

첫째 줄에 N, K가 주어진다. 둘째 줄에는 A1, A2, ..., A2N이 주어진다.

출력

몇 번째 단계가 진행 중일때 종료되었는지 출력한다.

제한

  • 2 ≤ N ≤ 100
  • 1 ≤ K ≤ 2N
  • 1 ≤ Ai ≤ 1,000

예제 입력 1

3 2 1 2 1 2 1 2

예제 출력 1

2

예제 입력 2

3 6

10 10 10 10 10 10

예제 출력 2

31

예제 입력 3

4 5

10 1 10 6 3 4 8 2

예제 출력 3

24

예제 입력 4

5 8

100 99 60 80 30 20 10 89 99 100

예제 출력 4

472

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;

class Pair {
    int d;  // durability
    boolean flag;  // has robot?
    Pair(int d, boolean flag) {
        this.d = d;
        this.flag = flag;
    }
}

public class Main {
    static ArrayList<Pair> list = new ArrayList<>();
    static int n, k;
    static int zeroCnt, ans;

    public static void process() {
        while(zeroCnt < k) {
            ans += 1;

            // 1. 벨트가 한 칸 회전한다.
            list.add(0, list.remove(2*n-1));
            list.get(n).flag = false;

            // 2. 가장 먼저 벨트에 올라간 로봇부터, 벨트가 회전하는 방향으로 한 칸 이동할 수 있다면 이동한다.
            // 만약 이동할 수 없다면 가만히 있는다.
            // 2.1 로봇이 이동하기 위해서는 이동하려는 칸에 로봇이 없으며, 그 칸의 내구도가 1 이상 남아 있어야 한다.
            for(int i = n-1; i >= 0; i--) {
                Pair now = list.get(i);
                Pair next = list.get(i+1);
                if(i == n-1) {  // 내려가는 칸 도착
                    now.flag = false;
                    continue;
                }
                if(now.flag && !next.flag && next.d > 0) {
                    now.flag = false;
                    next.flag = true;
                    next.d -= 1;
                    if(next.d == 0) {
                        zeroCnt += 1;
                    }
                }
            }

            // 3. 올라가는 위치에 로봇이 없다면 로봇을 하나 올린다.
            if(!list.get(0).flag && list.get(0).d > 0) {
                list.get(0).flag = true;
                list.get(0).d -= 1;
                if(list.get(0).d == 0) {
                    zeroCnt += 1;
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        k = Integer.parseInt(st.nextToken());

        st = new StringTokenizer(br.readLine());
        for(int i = 0; i < 2*n; i++) {
            int d = Integer.parseInt(st.nextToken());
            if(d == 0) zeroCnt += 1;
            list.add(new Pair(d, false));
        }

        process();
        System.out.println(ans);
    }
}

 

시키는대로 짜면 되는 구현 문제.

 

flag처리를 신경써가며 짜면 된다.

 

 

 

 

www.acmicpc.net/problem/14891

 

14891번: 톱니바퀴

총 8개의 톱니를 가지고 있는 톱니바퀴 4개가 아래 그림과 같이 일렬로 놓여져 있다. 또, 톱니는 N극 또는 S극 중 하나를 나타내고 있다. 톱니바퀴에는 번호가 매겨져 있는데, 가장 왼쪽 톱니바퀴

www.acmicpc.net

문제

총 8개의 톱니를 가지고 있는 톱니바퀴 4개가 아래 그림과 같이 일렬로 놓여져 있다. 또, 톱니는 N극 또는 S극 중 하나를 나타내고 있다. 톱니바퀴에는 번호가 매겨져 있는데, 가장 왼쪽 톱니바퀴가 1번, 그 오른쪽은 2번, 그 오른쪽은 3번, 가장 오른쪽 톱니바퀴는 4번이다.

이때, 톱니바퀴를 총 K번 회전시키려고 한다. 톱니바퀴의 회전은 한 칸을 기준으로 한다. 회전은 시계 방향과 반시계 방향이 있고, 아래 그림과 같이 회전한다.

톱니바퀴를 회전시키려면, 회전시킬 톱니바퀴와 회전시킬 방향을 결정해야 한다. 톱니바퀴가 회전할 때, 서로 맞닿은 극에 따라서 옆에 있는 톱니바퀴를 회전시킬 수도 있고, 회전시키지 않을 수도 있다. 톱니바퀴 A를 회전할 때, 그 옆에 있는 톱니바퀴 B와 서로 맞닿은 톱니의 극이 다르다면, B는 A가 회전한 방향과 반대방향으로 회전하게 된다. 예를 들어, 아래와 같은 경우를 살펴보자.

두 톱니바퀴의 맞닿은 부분은 초록색 점선으로 묶여있는 부분이다. 여기서, 3번 톱니바퀴를 반시계 방향으로 회전했다면, 4번 톱니바퀴는 시계 방향으로 회전하게 된다. 2번 톱니바퀴는 맞닿은 부분이 S극으로 서로 같기 때문에, 회전하지 않게 되고, 1번 톱니바퀴는 2번이 회전하지 않았기 때문에, 회전하지 않게 된다. 따라서, 아래 그림과 같은 모양을 만들게 된다.

위와 같은 상태에서 1번 톱니바퀴를 시계 방향으로 회전시키면, 2번 톱니바퀴가 반시계 방향으로 회전하게 되고, 2번이 회전하기 때문에, 3번도 동시에 시계 방향으로 회전하게 된다. 4번은 3번이 회전하지만, 맞닿은 극이 같기 때문에 회전하지 않는다. 따라서, 아래와 같은 상태가 된다.

톱니바퀴의 초기 상태와 톱니바퀴를 회전시킨 방법이 주어졌을 때, 최종 톱니바퀴의 상태를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 1번 톱니바퀴의 상태, 둘째 줄에 2번 톱니바퀴의 상태, 셋째 줄에 3번 톱니바퀴의 상태, 넷째 줄에 4번 톱니바퀴의 상태가 주어진다. 상태는 8개의 정수로 이루어져 있고, 12시방향부터 시계방향 순서대로 주어진다. N극은 0, S극은 1로 나타나있다.

다섯째 줄에는 회전 횟수 K(1 ≤ K ≤ 100)가 주어진다. 다음 K개 줄에는 회전시킨 방법이 순서대로 주어진다. 각 방법은 두 개의 정수로 이루어져 있고, 첫 번째 정수는 회전시킨 톱니바퀴의 번호, 두 번째 정수는 방향이다. 방향이 1인 경우는 시계 방향이고, -1인 경우는 반시계 방향이다.

출력

총 K번 회전시킨 이후에 네 톱니바퀴의 점수의 합을 출력한다. 점수란 다음과 같이 계산한다.

  • 1번 톱니바퀴의 12시방향이 N극이면 0점, S극이면 1점
  • 2번 톱니바퀴의 12시방향이 N극이면 0점, S극이면 2점
  • 3번 톱니바퀴의 12시방향이 N극이면 0점, S극이면 4점
  • 4번 톱니바퀴의 12시방향이 N극이면 0점, S극이면 8점

예제 입력 1

10101111

01111101

11001110

00000010

2

3 -1

1 1

예제 출력 1

7

예제 입력 2

11111111

11111111

11111111

11111111

3

1 1

2 1

3 1

예제 출력 2

15

예제 입력 3

10001011

10000011

01011011

00111101

5

1 1

2 1

3 1

4 1

1 -1

예제 출력 3

6

예제 입력 4

10010011

01010011

11100011

01010101

8

1 1

2 1

3 1

4 1

1 -1

2 -1

3 -1

4 -1

예제 출력 4

5

 

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static int[][] gear;
    static int[][] inst;
    static int k;

    public static int calculate() {
        int score = 0;
        if(gear[0][0] == 1) score += 1;
        if(gear[1][0] == 1) score += 2;
        if(gear[2][0] == 1) score += 4;
        if(gear[3][0] == 1) score += 8;
        return score;
    }

    public static int[] spin(int target, int dir) {
        int[] temp = new int[8];
        if(dir == 2) {
            temp[0] = gear[target][7];
            for(int i = 0; i < 7; i++) {
                temp[i+1] = gear[target][i];
            }
        }else {
            temp[7] = gear[target][0];
            for(int i = 1; i < 8; i++) {
                temp[i-1] = gear[target][i];
            }
        }
        return temp;
    }

    public static void process() {
        for(int i = 0; i < k; i++) {
            int target = inst[i][0];
            int dir = inst[i][1];

            // -1 : 회전X, 2 : 시계, 0 : 반시계
            int gear0 = -1, gear1 = -1, gear2 = -1, gear3 = -1;

            // 일단 대상 톱니 주변에 대해서만 처리
            if(target == 0) {
                gear0 = (dir == 1) ? 2 : 0;
                if(gear[0][2] != gear[1][6]) gear1 = 1 - dir;
            }else if(target == 1) {
                gear1 = (dir == 1) ? 2 : 0;
                if(gear[1][6] != gear[0][2]) gear0 = 1 - dir;
                if(gear[1][2] != gear[2][6]) gear2 = 1 - dir;
            }else if(target == 2) {
                gear2 = (dir == 1) ? 2 : 0;
                if(gear[1][2] != gear[2][6]) gear1 = 1 - dir;
                if(gear[2][2] != gear[3][6]) gear3 = 1 - dir;
            }else if(target == 3) {
                gear3 = (dir == 1) ? 2 : 0;
                if(gear[2][2] != gear[3][6]) gear2 = 1 - dir;
            }

            // 연쇄 작용 처리 (왼 -> 오)
            if(gear0 != -1 && gear1 == -1 && gear[0][2] != gear[1][6]) gear1 = (gear0 == 2) ? 0 : 2;  // gear0 <-> gear1
            if(gear1 != -1 && gear0 == -1 && gear[0][2] != gear[1][6]) gear0 = (gear1 == 2) ? 0 : 2;  // gear1 <-> gear0
            if(gear1 != -1 && gear2 == -1 && gear[1][2] != gear[2][6]) gear2 = (gear1 == 2) ? 0 : 2;  // gear1 <-> gear2
            if(gear2 != -1 && gear1 == -1 && gear[1][2] != gear[2][6]) gear1 = (gear2 == 2) ? 0 : 2;  // gear2 <-> gear1
            if(gear2 != -1 && gear3 == -1 && gear[2][2] != gear[3][6]) gear3 = (gear2 == 2) ? 0 : 2;  // gear2 <-> gear3
            if(gear3 != -1 && gear2 == -1 && gear[2][2] != gear[3][6]) gear2 = (gear3 == 2) ? 0 : 2;  // gear3 <-> gear2

            // 연쇄 작용 처리 (오 -> 왼)
            if(gear3 != -1 && gear2 == -1 && gear[2][2] != gear[3][6]) gear2 = (gear3 == 2) ? 0 : 2;  // gear3 <-> gear2
            if(gear2 != -1 && gear3 == -1 && gear[2][2] != gear[3][6]) gear3 = (gear2 == 2) ? 0 : 2;  // gear2 <-> gear3
            if(gear2 != -1 && gear1 == -1 && gear[1][2] != gear[2][6]) gear1 = (gear2 == 2) ? 0 : 2;  // gear2 <-> gear1
            if(gear1 != -1 && gear2 == -1 && gear[1][2] != gear[2][6]) gear2 = (gear1 == 2) ? 0 : 2;  // gear1 <-> gear2
            if(gear1 != -1 && gear0 == -1 && gear[0][2] != gear[1][6]) gear0 = (gear1 == 2) ? 0 : 2;  // gear1 <-> gear0
            if(gear0 != -1 && gear1 == -1 && gear[0][2] != gear[1][6]) gear1 = (gear0 == 2) ? 0 : 2;  // gear0 <-> gear1

            for(int j = 0; j < 4; j++) {
                if(j == 0 && gear0 == -1) continue;
                if(j == 1 && gear1 == -1) continue;
                if(j == 2 && gear2 == -1) continue;
                if(j == 3 && gear3 == -1) continue;

                if(j == 0)  gear[0] = spin(j, gear0);
                if(j == 1)  gear[1] = spin(j, gear1);
                if(j == 2)  gear[2] = spin(j, gear2);
                if(j == 3)  gear[3] = spin(j, gear3);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;

        gear = new int[4][8];
        for(int i = 0; i < 4; i++) {
            String str = br.readLine();
            for(int j = 0; j < 8; j++) {
                gear[i][j] = str.charAt(j) - '0';
            }
        }
        k = Integer.parseInt(br.readLine());
        inst = new int[k][2];
        for(int i = 0; i < k; i++) {
            st = new StringTokenizer(br.readLine());
            inst[i][0] = Integer.parseInt(st.nextToken()) - 1;
            inst[i][1] = Integer.parseInt(st.nextToken());
        }

        process();
        int ans = calculate();
        System.out.println(ans);
    }
}

 

처음으로 풀어본 삼성 스타일이 빡구현 문제.

특별한 알고리즘 기법 없이 그냥 시키는대로 구현하는 문제이다.

 

각 톱니바퀴의 상태가 1차원 배열에 저장되어있을 때, 옆 톱니바퀴와 만나는 톱니의 위치를 아래와 같이 구했다.

[0][2] <-> [1][6]

[1][2] <-> [2][6]

[2][2] <-> [3][6]

 

톱니바퀴가 돌아가며 그 옆의 톱니까지 돌아가는 연쇄 작용의 구현에 주의해야 한다.

 

target 톱니바퀴가 어느 위치일지 알 수 없기 때문에 target 주위의 톱니바퀴를 우선 처리해준 뒤 왼->오, 오->왼 방향으로 한 번씩 검사를 더 해줘야 한다.

 

 

 

 

www.acmicpc.net/problem/14888

 

14888번: 연산자 끼워넣기

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 

www.acmicpc.net

문제

N개의 수로 이루어진 수열 A1, A2, ..., AN이 주어진다. 또, 수와 수 사이에 끼워넣을 수 있는 N-1개의 연산자가 주어진다. 연산자는 덧셈(+), 뺄셈(-), 곱셈(×), 나눗셈(÷)으로만 이루어져 있다.

우리는 수와 수 사이에 연산자를 하나씩 넣어서, 수식을 하나 만들 수 있다. 이때, 주어진 수의 순서를 바꾸면 안 된다.

예를 들어, 6개의 수로 이루어진 수열이 1, 2, 3, 4, 5, 6이고, 주어진 연산자가 덧셈(+) 2개, 뺄셈(-) 1개, 곱셈(×) 1개, 나눗셈(÷) 1개인 경우에는 총 60가지의 식을 만들 수 있다. 예를 들어, 아래와 같은 식을 만들 수 있다.

  • 1+2+3-4×5÷6
  • 1÷2+3+4-5×6
  • 1+2÷3×4-5+6
  • 1÷2×3-4+5+6

식의 계산은 연산자 우선 순위를 무시하고 앞에서부터 진행해야 한다. 또, 나눗셈은 정수 나눗셈으로 몫만 취한다. 음수를 양수로 나눌 때는 C++14의 기준을 따른다. 즉, 양수로 바꾼 뒤 몫을 취하고, 그 몫을 음수로 바꾼 것과 같다. 이에 따라서, 위의 식 4개의 결과를 계산해보면 아래와 같다.

  • 1+2+3-4×5÷6 = 1
  • 1÷2+3+4-5×6 = 12
  • 1+2÷3×4-5+6 = 5
  • 1÷2×3-4+5+6 = 7

N개의 수와 N-1개의 연산자가 주어졌을 때, 만들 수 있는 식의 결과가 최대인 것과 최소인 것을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 곱셈(×)의 개수, 나눗셈(÷)의 개수이다. 

출력

첫째 줄에 만들 수 있는 식의 결과의 최댓값을, 둘째 줄에는 최솟값을 출력한다. 연산자를 어떻게 끼워넣어도 항상 -10억보다 크거나 같고, 10억보다 작거나 같은 결과가 나오는 입력만 주어진다. 또한, 앞에서부터 계산했을 때, 중간에 계산되는 식의 결과도 항상 -10억보다 크거나 같고, 10억보다 작거나 같다.

예제 입력 1

2

5 6

0 0 1 0

예제 출력 1

30

30

예제 입력 2

3

3 4 5

1 0 1 0

예제 출력 2

35

17

예제 입력 3

6

1 2 3 4 5 6

2 1 1 1

예제 출력 3

54

-24

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;

public class Main {
    static int n, max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
    static int[] arr;
    static char[] operator;
    static boolean[] check;

    public static int calculate(int now, char oper, int dept) {
        switch(oper) {
            case '+' :
                return now += arr[dept];
            case '-' :
                return now -= arr[dept];
            case '*' :
                return now *= arr[dept];
            case '/' :
                return now /= arr[dept];
            default :  // 의미 없음
                return 0;
        }
    }

    public static void dfs(int dept, int calc) {
        if(dept == n) {
            max = Math.max(max, calc);
            min = Math.min(min, calc);
            return;
        }

        Set<Character> set = new HashSet<>();
        for(int i = 1; i < n; i++) {
            if(!check[i] && !set.contains(operator[i])) {
                set.add(operator[i]);
                check[i] = true;
                int next = calculate(calc, operator[i], dept);
                dfs(dept + 1, next);
                check[i] = false;
            }
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        n = Integer.parseInt(st.nextToken());
        arr = new int[n];
        st = new StringTokenizer(br.readLine());
        for(int i = 0; i < n; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        operator = new char[n];
        check = new boolean[n];
        int idx = 1;
        st = new StringTokenizer(br.readLine());
        for(int i = 0; i < 4; i++) {
            int num = Integer.parseInt(st.nextToken());
            for(int j = 0; j < num; j++) {
                switch(i) {
                    case 0:
                        operator[idx++] = '+';
                        break;
                    case 1:
                        operator[idx++] = '-';
                        break;
                    case 2:
                        operator[idx++] = '*';
                        break;
                    case 3:
                        operator[idx++] = '/';
                        break;
                }
            }
        }

        dfs(1, arr[0]);
        System.out.println(max);
        System.out.println(min);
    }
}

 

N과 M 문제와 거의 똑같은 백트래킹 문제.

 

min 뿐만 아니라 max도 Integer.MIN_VALUE로 초기화해야 한다.

0으로 초기화할 경우 모든 결과식이 음수일 경우에는 max값이 0으로 출력돼버리기 때문. 

 

이것때문에 잠깐동안 맞왜틀을 시전했다.

 

 

 

 

www.acmicpc.net/problem/14889

 

14889번: 스타트와 링크

예제 2의 경우에 (1, 3, 6), (2, 4, 5)로 팀을 나누면 되고, 예제 3의 경우에는 (1, 2, 4, 5), (3, 6, 7, 8)로 팀을 나누면 된다.

www.acmicpc.net

문제

오늘은 스타트링크에 다니는 사람들이 모여서 축구를 해보려고 한다. 축구는 평일 오후에 하고 의무 참석도 아니다. 축구를 하기 위해 모인 사람은 총 N명이고 신기하게도 N은 짝수이다. 이제 N/2명으로 이루어진 스타트 팀과 링크 팀으로 사람들을 나눠야 한다.

BOJ를 운영하는 회사 답게 사람에게 번호를 1부터 N까지로 배정했고, 아래와 같은 능력치를 조사했다. 능력치 Sij는 i번 사람과 j번 사람이 같은 팀에 속했을 때, 팀에 더해지는 능력치이다. 팀의 능력치는 팀에 속한 모든 쌍의 능력치 Sij의 합이다. Sij는 Sji와 다를 수도 있으며, i번 사람과 j번 사람이 같은 팀에 속했을 때, 팀에 더해지는 능력치는 Sij와 Sji이다.

N=4이고, S가 아래와 같은 경우를 살펴보자.

  1 2 3
4   5 6
7 1   2
3 4 5  

예를 들어, 1, 2번이 스타트 팀, 3, 4번이 링크 팀에 속한 경우에 두 팀의 능력치는 아래와 같다.

  • 스타트 팀: S12 + S21 = 1 + 4 = 5
  • 링크 팀: S34 + S43 = 2 + 5 = 7

1, 3번이 스타트 팀, 2, 4번이 링크 팀에 속하면, 두 팀의 능력치는 아래와 같다.

  • 스타트 팀: S13 + S31 = 2 + 7 = 9
  • 링크 팀: S24 + S42 = 6 + 4 = 10

축구를 재미있게 하기 위해서 스타트 팀의 능력치와 링크 팀의 능력치의 차이를 최소로 하려고 한다. 위의 예제와 같은 경우에는 1, 4번이 스타트 팀, 2, 3번 팀이 링크 팀에 속하면 스타트 팀의 능력치는 6, 링크 팀의 능력치는 6이 되어서 차이가 0이 되고 이 값이 최소이다.

입력

첫째 줄에 N(4 ≤ N ≤ 20, N은 짝수)이 주어진다. 둘째 줄부터 N개의 줄에 S가 주어진다. 각 줄은 N개의 수로 이루어져 있고, i번 줄의 j번째 수는 Sij 이다. Sii는 항상 0이고, 나머지 Sij는 1보다 크거나 같고, 100보다 작거나 같은 정수이다.

출력

첫째 줄에 스타트 팀과 링크 팀의 능력치의 차이의 최솟값을 출력한다.

예제 입력 1

4

0 1 2 3

4 0 5 6

7 1 0 2

3 4 5 0

예제 출력 1

0

예제 입력 2

6

0 1 2 3 4 5

1 0 2 3 4 5

1 2 0 3 4 5

1 2 3 0 4 5

1 2 3 4 0 5

1 2 3 4 5 0

예제 출력 2

2

예제 입력 3

8

0 5 4 5 4 5 4 5

4 0 5 1 2 3 4 5

9 8 0 1 2 3 1 2

9 9 9 0 9 9 9 9

1 1 1 1 0 1 1 1

8 7 6 5 4 0 3 2

9 1 9 1 9 1 0 9

6 5 4 3 2 1 9 0

예제 출력 3

1

 

 

 

 

 

 

풀이 1. (틀린 코드 - 시간 초과)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static int n, sTotal, lTotal, ans = 100000;
    static int[][] S;
    static int[] start, link;
    static boolean[] check;

    public static void calculate() {
        sTotal = 0;
        lTotal = 0;
        start = new int[n/2];
        link = new int[n/2];

        int sIdx = 0, lIdx = 0;
        for(int i = 0; i < n; i++) {
            if(check[i]) {
                start[sIdx++] = i;
            }else {
                link[lIdx++] = i;
            }
        }

        for(int i = 0; i < n/2; i++) {
            for(int j = i+1; j < n/2; j++) {
                sTotal += S[start[i]][start[j]] + S[start[j]][start[i]];
                lTotal += S[link[i]][link[j]] + S[link[j]][link[i]];
            }
        }
    }

    public static void dfs(int dept) {
        if(dept == n/2) {
            calculate();
            ans = Math.min(ans, Math.abs(sTotal - lTotal));
            return;
        }

        for(int i = 0; i < n; i++) {
            if(!check[i]) {
                check[i] = true;
                dfs(dept + 1);
                check[i] = false;
            }
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        S = new int[n][n];
        check = new boolean[n];
        for(int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j < n; j++) {
                S[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        dfs(0);
        System.out.println(ans);
    }
}

 

N제한은 최대 20. 위 방식대로 N/2개를 구하려면 20P10 = 약 6700억 개의 조합이 나온다.

백트래킹을 이용한 브루트포스를 사용해야 하는 것은 맞지만 시간을 좀 더 줄여야 한다.

 

 

 

 

 

풀이 2.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static int n, sTotal, lTotal, ans = 100000;
    static int[][] S;
    static int[] start, link;
    static boolean[] check;

    public static void calculate() {
        sTotal = 0;
        lTotal = 0;
        start = new int[n/2];
        link = new int[n/2];

        int sIdx = 0, lIdx = 0;
        for(int i = 0; i < n; i++) {
            if(check[i]) {
                start[sIdx++] = i;
            }else {
                link[lIdx++] = i;
            }
        }

        for(int i = 0; i < n/2; i++) {
            for(int j = i+1; j < n/2; j++) {
                sTotal += S[start[i]][start[j]] + S[start[j]][start[i]];
                lTotal += S[link[i]][link[j]] + S[link[j]][link[i]];
            }
        }
    }

    public static void dfs(int idx, int dept) {
        if(dept == n/2) {
            calculate();
            ans = Math.min(ans, Math.abs(sTotal - lTotal));
            return;
        }

        for(int i = idx; i < n; i++) {  // 중복조합 제외하기 위해 idx부터 검사
            if(!check[i]) {
                check[i] = true;
                dfs(i, dept + 1);
                check[i] = false;
            }
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        S = new int[n][n];
        check = new boolean[n];
        for(int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j < n; j++) {
                S[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        dfs(0, 0);
        System.out.println(ans);
    }
}

 

이전 방법은 중복조합까지 모두 검사하기 때문에 경우의 수가 너무 많았다.

 

int idx를 추가하여 중복 조합을 배제했다.

20P10( = 약 6700억)을 20C10( =  약 18만)으로 줄여서 시간 초과를 벗어났다.

 

 

 

사용되는 알고리즘은 흔한 것이지만 구현에서 헷갈리는 부분이 많아 많이 해맸다.

 

 

 

 

www.acmicpc.net/problem/14501

 

14501번: 퇴사

첫째 줄에 백준이가 얻을 수 있는 최대 이익을 출력한다.

www.acmicpc.net

문제

상담원으로 일하고 있는 백준이는 퇴사를 하려고 한다.

오늘부터 N+1일째 되는 날 퇴사를 하기 위해서, 남은 N일 동안 최대한 많은 상담을 하려고 한다.

백준이는 비서에게 최대한 많은 상담을 잡으라고 부탁을 했고, 비서는 하루에 하나씩 서로 다른 사람의 상담을 잡아놓았다.

각각의 상담은 상담을 완료하는데 걸리는 기간 Ti와 상담을 했을 때 받을 수 있는 금액 Pi로 이루어져 있다.

N = 7인 경우에 다음과 같은 상담 일정표를 보자.

 1일2일3일4일5일6일7일TiPi

3 5 1 1 2 4 2
10 20 10 20 15 40 200

1일에 잡혀있는 상담은 총 3일이 걸리며, 상담했을 때 받을 수 있는 금액은 10이다. 5일에 잡혀있는 상담은 총 2일이 걸리며, 받을 수 있는 금액은 15이다.

상담을 하는데 필요한 기간은 1일보다 클 수 있기 때문에, 모든 상담을 할 수는 없다. 예를 들어서 1일에 상담을 하게 되면, 2일, 3일에 있는 상담은 할 수 없게 된다. 2일에 있는 상담을 하게 되면, 3, 4, 5, 6일에 잡혀있는 상담은 할 수 없다.

또한, N+1일째에는 회사에 없기 때문에, 6, 7일에 있는 상담을 할 수 없다.

퇴사 전에 할 수 있는 상담의 최대 이익은 1일, 4일, 5일에 있는 상담을 하는 것이며, 이때의 이익은 10+20+15=45이다.

상담을 적절히 했을 때, 백준이가 얻을 수 있는 최대 수익을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N (1 ≤ N ≤ 15)이 주어진다.

둘째 줄부터 N개의 줄에 Ti와 Pi가 공백으로 구분되어서 주어지며, 1일부터 N일까지 순서대로 주어진다. (1 ≤ Ti ≤ 5, 1 ≤ Pi ≤ 1,000)

출력

첫째 줄에 백준이가 얻을 수 있는 최대 이익을 출력한다.

예제 입력 1

7

3 10

5 20

1 10

1 20

2 15

4 40

2 200

예제 출력 1

45

예제 입력 2

10

1 1

1 2

1 3

1 4

1 5

1 6

1 7

1 8

1 9

1 10

예제 출력 2

55

예제 입력 3

10

5 10

5 9

5 8

5 7

5 6

5 10

5 9

5 8

5 7

5 6

예제 출력 3

20

예제 입력 4

10

5 50

4 40

3 30

2 20

1 10

1 10

2 20

3 30

4 40

5 50

예제 출력 4

90

 

 

 

 

 

 

 

풀이 1. (완전 탐색)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static int n, ans;
    static int[] T;
    static int[] P;

    public static void dfs(int dept, int finish, int sum) {
        if(dept == n+1) {
            ans = Math.max(ans, sum);
            return;
        }

        if(dept >= finish && dept + T[dept] <= n+1) {
            dfs(dept+1, dept + T[dept], sum + P[dept]);
        }
        dfs(dept+1, finish, sum);
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        T = new int[n+1];
        P = new int[n+1];
        for(int i = 1; i <= n; i++) {
            st = new StringTokenizer(br.readLine());
            T[i] = Integer.parseInt(st.nextToken());
            P[i] = Integer.parseInt(st.nextToken());
        }

        dfs(1, 0, 0);
        System.out.println(ans);
    }
}

 

완전 탐색을 이용한 O(2^N) 풀이.

N제한이 15로 매우 작은 덕분에 완전 탐색도 무리가 없다. (매번 선택을 할 수 있는 것은 아니니 실제로는 2^N보다 훨씬 적을 것)

 

 

 

 

 

 

 

풀이 2. (DP)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int[] T = new int[n+1];
        int[] P = new int[n+1];
        for(int i = 1; i <= n; i++) {
            st = new StringTokenizer(br.readLine());
            T[i] = Integer.parseInt(st.nextToken());
            P[i] = Integer.parseInt(st.nextToken());
        }

        int[] dp = new int[n+1];  // dp[n] == n번째 날에 "일을 할 때" 벌 수 있는 최고 금액
        dp[0] = 0;
        int ans = dp[0];
        for(int i = 1; i <= n; i++) {
            if(i + T[i] <= n+1) {  // 이걸 선택하면 퇴사 전에 일을 끝낼 수 있는가?
                for(int j = 0; j < i; j++) {
                    if(j + T[j] <= i) {  // j날에 상담한 후 i날에 상담할 수 있는가?
                        dp[i] = Math.max(dp[i], dp[j] + P[i]);
                    }
                }
                ans = Math.max(ans, dp[i]);
            }
        }
        System.out.println(ans);
    }
}

 

DP를 이용한 O(N^2) 풀이.

N제한이 커졌을 경우엔 DP를 사용해야 한다.

 

완전 탐색 풀이에서는 인덱스 구분을 쉽게 하기 위해서 n+1크기로 배열을 만들었지만 여기선 그런 편리함의 문제가 아니더라도 반드시 n+1로 선언해야 한다.

 

아무것도 선택하지 않다가 i번째 날에 선택을 하는 경우를 위해서 dp[0] = 0이 반드시 필요하기 때문.

 

 

 

 

www.acmicpc.net/problem/13458

 

13458번: 시험 감독

첫째 줄에 시험장의 개수 N(1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에는 각 시험장에 있는 응시자의 수 Ai (1 ≤ Ai ≤ 1,000,000)가 주어진다. 셋째 줄에는 B와 C가 주어진다. (1 ≤ B, C ≤ 1,000,000)

www.acmicpc.net

문제

총 N개의 시험장이 있고, 각각의 시험장마다 응시자들이 있다. i번 시험장에 있는 응시자의 수는 Ai명이다.

감독관은 총감독관과 부감독관으로 두 종류가 있다. 총감독관은 한 시험장에서 감시할 수 있는 응시자의 수가 B명이고, 부감독관은 한 시험장에서 감시할 수 있는 응시자의 수가 C명이다.

각각의 시험장에 총감독관은 오직 1명만 있어야 하고, 부감독관은 여러 명 있어도 된다.

각 시험장마다 응시생들을 모두 감시해야 한다. 이때, 필요한 감독관 수의 최솟값을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 시험장의 개수 N(1 ≤ N ≤ 1,000,000)이 주어진다.

둘째 줄에는 각 시험장에 있는 응시자의 수 Ai (1 ≤ Ai ≤ 1,000,000)가 주어진다.

셋째 줄에는 B와 C가 주어진다. (1 ≤ B, C ≤ 1,000,000)

출력

각 시험장마다 응시생을 모두 감독하기 위해 필요한 감독관의 최소 수를 출력한다.

예제 입력 1

1

1

1 1

예제 출력 1

1

예제 입력 2

3

3 4 5

2 2

예제 출력 2

7

예제 입력 3

5

1000000 1000000 1000000 1000000 1000000

5 7

예제 출력 3

714290

예제 입력 4

5

10 9 10 9 10

7 20

예제 출력 4

10

예제 입력 5

5

10 9 10 9 10

7 2

예제 출력 5

13

 

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int[] A = new int[n];
        st = new StringTokenizer(br.readLine());
        for(int i = 0; i < n; i++) {
            A[i] = Integer.parseInt(st.nextToken());
        }
        st = new StringTokenizer(br.readLine());
        int B = Integer.parseInt(st.nextToken());
        int C = Integer.parseInt(st.nextToken());

        boolean flag = true;  // true : 총 > 부, false : 부 > 총
        long ans = 0;
        for(int i = 0; i < n; i++) {
            int num = A[i];
            if(flag) {  // 총감1 + 부감
                num -= B;
                ans += 1;  // 총감 1명
                if(num > 0) {  // 총감으로 해결 안 되면
                    if(num % C == 0) {
                        ans += num / C;
                    }else {
                        ans += (num / C) + 1;
                    }
                }
            }else {  // 전부 부감
                if(num % C == 0) {
                    ans += num / C;
                }else {
                    ans += (num / C) + 1;
                }
            }
        }
        System.out.println(ans);
    }
}

 

간단한 구현 문제.

 

총감독관은 1명만 들어가야 한다는 부분을 주의해야 한다. 1명만 들어가야 한다는 말이 1명이 반드시 들어가야 한다는 것은 아니다. 만약 들어간다면 1명까지만 들어갈 수 있다는 것.

 

따라서 부감독관이 더 많은 인원을 감시할 수 있다면 총감독관을 쓰지 않도록 하는 것이 옳다.

 

범위를 고려하지 않고 int를 써서 한 번 들렸다.

N은 최대 백만이고 Ai도 최대 백만이기 때문에 ans의 최대값은 10^12승이 되어 int 범위를 벗어난다.

 

 

 

 

+ Recent posts