문제링크 : https://www.acmicpc.net/problem/2580

 

2580번: 스도쿠

스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루어진 정사각형 판 위에서 이뤄지는데, 게임 시작 전 몇 몇 칸에는 1부터 9까지의 숫자 중 하나가 쓰여 있다. 나머지 빈 칸을 채우는 방식은 다음과 같다. 각각의 가로줄과 세로줄에는 1부터 9까지의 숫자가 한 번씩만 나타나야 한다. 굵은 선으로 구분되어 있는 3

www.acmicpc.net

단순하게만 생각한다면, '빈 칸이 포함되는 가로줄, 세로줄, 9개의 네모 칸 안의 수를 다 비교해서, 빈칸에 넣을 수 있는 수를 넣으면 되는 거 아닌가?'라고 생각할 수 있다. 이것도 틀린 말은 아니나 소싯적(?) 스도쿠를 조금이라도 해본 사람은 이게 그렇게 간단한 문제는 아니라는 걸 알 것이다. 다 비교해봐도 빈 칸 안에 넣을 수 있는 수가 1개가 넘는 경우가 많기 때문이다. 심지어 이 문제에는 '판을 채우는 방법이 여럿인 경우는 그 중 하나만을 출력한다.'라는 조건이 있다. 그 말은 81개의 모든 칸이 0으로 주어진다고 해도 답이 나와야한다는 얘기다. 뭔가 완전 탐색의 냄새(?)가 나지 않는가?ㅋㅋ 풀이에 대한 설명을 시작하겠다. 일단 이 풀이는 DFS 백트래킹이며, 자세한 건 코드를 보고 설명하는 편이 나을 듯하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
 
public class Main {
    private static boolean finished = false;
    
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int[][] puzzle = new int[9][9];
        List<Point> zeroList = new ArrayList<Point>();
        
        for(int i = 0; i < 9; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            
            for(int j = 0; j < 9; j++) {
                puzzle[i][j] = Integer.parseInt(st.nextToken());
                
                if(puzzle[i][j] == 0) {
                    zeroList.add(new Point(i, j));
                }
            }
        }
        
        if(zeroList.size() > 0) {
            dfs(zeroList.get(0), puzzle, zeroList, 0);
        } else {
            printPuzzle(puzzle);
        }    
    }
 
    private static void dfs(Point point, int[][] puzzle, List<Point> zeroList, int depth) {
        if(finished) {
            return;
        }
        
        boolean[] checked = getCandidates(puzzle, point);
        
        for(int k = 1; k <= 9; k++) {
            if(!checked[k]) {
                int i = point.getI();
                int j = point.getJ();
                
                puzzle[i][j] = k;
                
                if(depth == zeroList.size() - 1) {
                    printPuzzle(puzzle);
                    finished = true;
                    return;
                }
                
                dfs(zeroList.get(depth + 1), puzzle, zeroList, depth + 1);            
                puzzle[i][j] = 0;
            }
        }
    }
 
    private static void printPuzzle(int[][] puzzle) {
        StringBuffer sb = new StringBuffer();
        
        for(int i = 0; i < 9; i++) {
            for(int j = 0; j < 9; j++) {
                sb.append(puzzle[i][j] + " ");
            }
            
            sb.append("\n");
        }
        
        System.out.println(sb);
    }
    
    private static boolean[] getCandidates(int[][] puzzle, Point point) {
        int i = point.getI();
        int j = point.getJ();
        boolean[] checked = new boolean[10];
        
        for(int k = 0; k < 9; k++) {
            checked[puzzle[i][k]] = true;
        }
        
        for(int k = 0; k < 9; k++) {
            checked[puzzle[k][j]] = true;
        }
        
        int sI = i / 3 * 3;
        int sJ = j / 3 * 3;
        
        for(int k = sI; k < sI + 3; k++) {
            for(int h = sJ; h < sJ + 3; h++) {
                checked[puzzle[k][h]] = true;
            }
        }
        
        return checked;
    }
    
    private static class Point {
        private int i;
        private int j;
        
        public Point(int i, int j) {
            this.i = i;
            this.j = j;
        }
 
        public int getI() {
            return i;
        }
 
        public int getJ() {
            return j;
        }    
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

1. 빈 칸의 위치를 모두 List에 저장한다.

2. 첫번째 빈 칸에 들어갈 수 있는 모든 수를 구한 후, 하나씩 넣은 후, DFS로 다음 빈 칸으로 이동한다.

3. 다음 빈 칸에 대해서도 동일하게(재귀함수) 들어갈 수 있는 모든 수를 구한 후, 하나씩 넣고 다음 칸으로 이동한다.

4. 만약 List에 저장되있는 모든 빈 칸에 대해 숫자가 채워졌다면 함수 실행을 멈추고 값을 출력한다.

5. 만약 현재 넣어본 수가 스도쿠의 조건을 만족할 수 없는 수라면, DFS를 도는 어느 순간, 해당 빈 칸에 들어갈 수 있는 수가 0개로 나올 것이고 해당 조건은 모든 빈 칸을 채울 수 없을 것이다. 해당 depth를 충족시킬 수 없을 것이다. 

6. 아까 빈 칸에 들어갈 수 있는 여러가지 수 중 하나를 일단 넣어봤다. 다른 경우의 수도 알아보려면 아까 수를 넣기 이전 상태로 초기화가 되어야한다. 칸을 다시 빈 칸(0)으로 초기화해준다.(백트래킹)

 

근데 상위권 풀이에 비해 실행시간이 다소 길다...-_- 상위권 풀이를 살펴보니, 굳이 빈칸에 넣을 수 있는 수를 매번 구할 필요가 없다라는 걸 깨달았다. 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
 
public class Main {
    private static boolean finished = false;
    private static boolean[][] checkI = new boolean[9][10];
    private static boolean[][] checkJ = new boolean[9][10];
    private static boolean[][][] checkBox = new boolean[3][3][10];
    
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int[][] puzzle = new int[9][9];
        List<Point> zeroList = new ArrayList<Point>();
        
        for(int i = 0; i < 9; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            
            for(int j = 0; j < 9; j++) {
                puzzle[i][j] = Integer.parseInt(st.nextToken());
                
                if(puzzle[i][j] == 0) {
                    zeroList.add(new Point(i, j));
                } else {
                    checkI[i][puzzle[i][j]] = true;
                    checkJ[j][puzzle[i][j]] = true;
                    checkBox[i / 3][j / 3][puzzle[i][j]] = true;
                }
            }
        }
        
        if(zeroList.size() > 0) {
            dfs(zeroList.get(0), puzzle, zeroList, 0);
        } else {
            printPuzzle(puzzle);
        }    
    }
 
    private static void dfs(Point point, int[][] puzzle, List<Point> zeroList, int depth) {
        if(finished) {
            return;
        }
        
        int i = point.getI();
        int j = point.getJ();
        
        for(int k = 1; k <= 9; k++) {
            if(!checkI[i][k] && !checkJ[j][k] && !checkBox[i / 3][j / 3][k]) {
                
                puzzle[i][j] = k;
                checkI[i][k] = true;
                checkJ[j][k] = true;
                checkBox[i / 3][j / 3][k] = true;
                
                if(depth == zeroList.size() - 1) {
                    printPuzzle(puzzle);
                    finished = true;
                    return;
                }
                
                dfs(zeroList.get(depth + 1), puzzle, zeroList, depth + 1);            
                puzzle[i][j] = 0;
                checkI[i][k] = false;
                checkJ[j][k] = false;
                checkBox[i / 3][j / 3][k] = false;
            }
        }
    }
 
    private static void printPuzzle(int[][] puzzle) {
        StringBuffer sb = new StringBuffer();
        
        for(int i = 0; i < 9; i++) {
            for(int j = 0; j < 9; j++) {
                sb.append(puzzle[i][j] + " ");
            }
            
            sb.append("\n");
        }
        
        System.out.println(sb);
    }
 
    private static class Point {
        private int i;
        private int j;
        
        public Point(int i, int j) {
            this.i = i;
            this.j = j;
        }
 
        public int getI() {
            return i;
        }
 
        public int getJ() {
            return j;
        }    
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

문제링크 : https://www.acmicpc.net/problem/2186

 

2186번: 문자판

첫째 줄에 N(1 ≤ N ≤ 100), M(1 ≤ M ≤ 100), K(1 ≤ K ≤ 5)가 주어진다. 다음 N개의 줄에는 M개의 알파벳 대문자가 주어지는데, 이는 N×M 크기의 문자판을 나타낸다. 다음 줄에는 1자 이상 80자 이하의 영단어가 주어진다. 모든 문자들은 알파벳 대문자이며, 공백 없이 주어진다.

www.acmicpc.net

DFS로 풀면 되는 문제다. 단순 DFS로 풀었을 때의 시간복잡도는... 아... 어떻게 계산해야할까-_- (필자가 나중에 시간이 되면 계산에 도전해보도록 하겠다;;;) 하여간 어마어마(?)할 것이다. 한 번의 탐색에서 같은 칸을 방문할 수 없다고 한다고 해도 시간복잡도가 어마어마할텐데(이 경우에는 백트래킹으로 풀 것이다), 심지어 같은 칸을 여러 번 방문할 수 있다고 한다. 단순 DFS로 풀면 안 된다; 뭔가 대책이 필요하다. 바로 메모이제이션이다. 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
 
public class Main {
    private static int[] di = {-1100};
    private static int[] dj = {00-11};
    private static int n;
    private static int m;
    private static int k;
    private static int trgtLen;
    private static int[][][] dp;
    
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        k = Integer.parseInt(st.nextToken());
        
        char[][] board = new char[n][m];
        
        for(int i = 0; i < n; i++) {
            board[i] = br.readLine().toCharArray();
        }
        
        char[] goal = br.readLine().toCharArray();
        trgtLen = goal.length;
        dp = new int[n][m][trgtLen];
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                Arrays.fill(dp[i][j], -1);
            }
        }
        
        int cnt = 0;
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {                
                if(board[i][j] == goal[0]) {
                    cnt += dfs(board, goal, i, j, 0);
                }
            }
        }
        
        System.out.println(cnt);
    }
 
    private static int dfs(char[][] board, char[] goal, int i, int j, int depth) {
        
        if(dp[i][j][depth] != -1) {
            return dp[i][j][depth];
        }
        
        if(depth == trgtLen - 1) {
            return 1;
        }
        
        int cnt = 0;
        
        for(int a = 1; a <= k; a++) {
            for(int b = 0; b < 4; b++) {
                int nextI = i + di[b] * a;
                int nextJ = j + dj[b] * a;
                
                if(nextI >= 0 && nextJ >= 0 && nextI < n && nextJ < m && board[nextI][nextJ] == goal[depth + 1]) {
                    cnt += dfs(board, goal, nextI, nextJ, depth + 1);
                }
            }
        }
        
        return dp[i][j][depth] = cnt;
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

여기서 dp[i][j][x]가 가리키는 값은, 문자판의 (i, j)의 위치를 depth=x 인 경우에 방문했을 때 나올 수 있는 경로의 수이다.

 

위 그림을 통해 좀 더 설명해보겠다. BREAK를 탐색하는 문제라고 가정했을 때 처음 윗쪽 B에서 탐색을 시작한다. 그리고 빨간색 E를 주목하자. E에서는 각각 좌우로 A, K로 가면 답이 나온다. 즉 이 위치에서 정답으로 갈 수 있는 경우의 수는 2개이다. 그리고 이번엔 밑쪽 B에서 탐색을 시작해보자. E에 도착했을 때, 굳이 A, K로 또 탐색을 할 필요가 있을까? 이미 여기에서 정답이 나오는 경우의 수가 2개라는 걸 알지 않는가? 이 정도 설명으로 이해가 됐으리라고 본다. '메모이제이션을 할 때 depth까진 저장할 필요가 없지 않나?'란 생각을 할 수도 있다. 근데 만들어야하는 영단어가 'AABAAA'와 같은, 같은 알파벳이 중복된 것일 수도 있기 때문에 depth에 따른 경우의 수를 저장해야한다.

 

 

문제링크 : https://www.acmicpc.net/problem/11053

 

11053번: 가장 긴 증가하는 부분 수열

수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오. 예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4이다.

www.acmicpc.net

총 2가지의 풀이를 소개할 것이다. 1. 내가 이 문제를 푼 방법 2. 상위권 풀이에 있는 방법

 

내 풀이에서 생각해야할 점은 다음과 같다.

1. 수열(seq)의 크기와 똑같은 크기의 배열 max를 선언한다.

2. max[i]가 뜻하는 것은, seq[i]가 선택되었을 때 증가하는 부분 수열 중 가장 긴 길이의 값이다.

 

코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[] seq = new int[n];
        int[] max = new int[n];
        
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        for(int i = 0; i < n; i++) {
            seq[i] = Integer.parseInt(st.nextToken());
        }
        
        max[0= 1;
        
        for(int i = 1; i < n; i++) {
            max[i] = 1;
            
            for(int j = i - 1; j >= 0; j--) {
                
                if(seq[j] < seq[i] && max[j] + 1 >  max[i]) {
                    max[i] = max[j] + 1;
                }
            }
        }
        
        int answer = max[n - 1];
        
        for(int i = n - 2; i >= 0; i--) {
            
            if(max[i] > answer) {
                answer = max[i];
            }
        }
 
        System.out.println(answer);
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

두 번재 풀이의 경우, 일단 코드부터 먼저 소개하겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[] seq = new int[n];
        int[] partSeq = new int[n];
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        for(int i = 0; i < n; i++) {
            seq[i] = Integer.parseInt(st.nextToken());
            partSeq[i] = -1;
        }
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                if(partSeq[j] == -1 || partSeq[j] >= seq[i]) {
                    partSeq[j] = seq[i];
                    break;
                }
            }
        }
        
        int answer = 0;
        
        for(int i = 0; i < n; i++) {
            if(partSeq[i] > 0) {
                answer++;
            }
        }
        
        System.out.println(answer);
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

하나의 수열을 예를 들어서 반복문에 대한 디버깅을 해보자.

일단 가장 긴 증가하는 부분수열은 i = 8일 때의 partSeq가 될 것이다. 그 때의 부분수열이 길이 6까지 도달하였으므로 최댓값이 6이 되는 것이다. 가장 멀리까지 간 놈의 길이를 구한다고 생각하면 이해하기 쉽다. 설명하기가 참 어렵다. 아마 필자도 '어떻게 이런 생각을 하지?'란 생각이 들기 때문인 것 같다....;;; 필자가 푼 풀이와 동일한 시간복잡도 O(N^2)을 가지고 있으나 중간에 break로 빠져나오는 부분이 있어서 조금 더 유리한 것 같다.

 

문제링크 : https://www.acmicpc.net/problem/1451

 

1451번: 직사각형으로 나누기

첫째 줄에 직사각형의 세로 크기 N과 가로 크기 M이 주어진다. 둘째 줄부터 직사각형에 들어가는 수가 가장 윗 줄부터 한 줄에 하나씩 M개의 수가 주어진다. N과 M은 100보다 작거나 같은 자연수이고, 직사각형엔 적어도 3개의 수가 있다. 또, 직사각형에 들어가는 수는 한 자리의 숫자이다.

www.acmicpc.net

내가 가장 싫어하는 전형적인 그리디 문제이다-_- 아... 이런 문제 진짜 싫다. 하나라도 빠뜨리면 안되는... 실수할 여지도 너무 많고...ㅠㅠ 아무튼 문제에 대한 설명을 해보겠다. 직사각형을 작은 3개의 직사각형으로 나눌 수 있는 경우의 수는 아래 그림과 같이 총 6가지이다.

 

이렇게 총 6가지 경우에 대해 모두 계산을 해서 이 중에 가장 큰 값을 구하면 된다... 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        int[][] rectangle = new int[n][m];
        
        for(int i = 0; i < n; i++) {
            char[] temp = br.readLine().toCharArray();
            
            for(int j = 0; j < m; j++) {
                rectangle[i][j] = temp[j] - '0';
            }
        }
        
        long max = 0;
        
        for(int i = 1; i < n; i++) {
            long a = getRectangleSum(0, i, 0, m, rectangle);
            
            for(int j = 1; j < m; j++) {
                long b = getRectangleSum(i, n, 0, j, rectangle);
                long c = getRectangleSum(i, n, j, m, rectangle);
                
                long tmp = a * b * c;
                
                if(max < tmp) {
                    max = tmp;
                }
            }
            
            for(int j = i + 1; j < n; j++) {
                long b = getRectangleSum(i, j, 0, m, rectangle);
                long c = getRectangleSum(j, n, 0, m, rectangle);
                
                long tmp = a * b * c;
                
                if(max < tmp) {
                    max = tmp;
                }
            }    
        }
        
        for(int i = n - 1; i > 0; i--) {
            long a = getRectangleSum(i, n, 0, m, rectangle);
            
            for(int j = 1; j < m; j++) {
                long b = getRectangleSum(0, i, 0, j, rectangle);
                long c = getRectangleSum(0, i, j, m, rectangle);
                
                long tmp = a * b * c;
                
                if(max < tmp) {
                    max = tmp;
                }
            }
        }
        
        for(int i = 1; i < m; i++) {
            long a = getRectangleSum(0, n, 0, i, rectangle);
            
            for(int j = 1; j < n; j++) {
                long b = getRectangleSum(0, j, i, m, rectangle);
                long c = getRectangleSum(j, n, i, m, rectangle);
                
                long tmp = a * b * c;
                
                if(max < tmp) {
                    max = tmp;
                }
            }
            
            for(int j = i + 1; j < m; j++) {
                long b = getRectangleSum(0, n, i, j, rectangle);
                long c = getRectangleSum(0, n, j, m, rectangle);
                
                long tmp = a * b * c;
                
                if(max < tmp) {
                    max = tmp;
                }
            }    
        }
        
        for(int i = m - 1; i > 0; i--) {
            long a = getRectangleSum(0, n, i, m, rectangle);
            
            for(int j = 1; j < n; j++) {
                long b = getRectangleSum(0, j, 0, i, rectangle);
                long c = getRectangleSum(j, n, 0, i, rectangle);
                
                long tmp = a * b * c;
                
                if(max < tmp) {
                    max = tmp;
                }
            }
        }
        
        System.out.println(max);
    }
    
    private static long getRectangleSum(int sI, int eI, int sJ, int eJ, int[][] rectangle) {
        long sum = 0;
        
        for(int i = sI; i < eI; i++) {
            for(int j = sJ; j < eJ; j++) {
                sum += rectangle[i][j];
            }
        }
        
        return sum;
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

뭔가 메서드로 빼고 싶은 욕구가 용솟음친다... 근데 딱히 뺄 게 없어 보였다. 그나마 메서드로 뺀 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        int[][] rectangle = new int[n][m];
        
        for(int i = 0; i < n; i++) {
            char[] temp = br.readLine().toCharArray();
            
            for(int j = 0; j < m; j++) {
                rectangle[i][j] = temp[j] - '0';
            }
        }
        
        long max = 0;
        
        for(int i = 1; i < n; i++) {
            long a = getRectangleSum(0, i, 0, m, rectangle);
            
            max = getMaxVertical(a, rectangle, max, i, n, 0, m);    
            max = getMaxHorizontal(a, rectangle, max, i, n, 0, m);
        }
                
        for(int i = n - 1; i > 0; i--) {
            long a = getRectangleSum(i, n, 0, m, rectangle);
            
            max = getMaxVertical(a, rectangle, max, 0, i, 0, m);        
        }
        
        for(int i = 1; i < m; i++) {
            long a = getRectangleSum(0, n, 0, i, rectangle);
            
            max = getMaxHorizontal(a, rectangle, max, 0, n, i, m);
            max = getMaxVertical(a, rectangle, max, 0, n, i, m);        
        }
        
        for(int i = m - 1; i > 0; i--) {
            long a = getRectangleSum(0, n, i, m, rectangle);
            
            max = getMaxHorizontal(a, rectangle, max, 0, n, 0, i);
        }
        
        System.out.println(max);
    }
 
    private static long getMaxHorizontal(long a, int[][] rectangle, long max, int sI, int eI, int sJ, int eJ) {
        
        for(int j = sI + 1; j < eI; j++) {
            long b = getRectangleSum(sI, j, sJ, eJ, rectangle);
            long c = getRectangleSum(j, eI, sJ, eJ, rectangle);
            
            long tmp = a * b * c;
            
            if(max < tmp) {
                max = tmp;
            }
        }
        
        return max;
    }
 
    private static long getMaxVertical(long a, int[][] rectangle, long max, int sI, int eI, int sJ, int eJ) {
        
        for(int j = sJ + 1; j < eJ; j++) {
            long b = getRectangleSum(sI, eI, sJ, j, rectangle);
            long c = getRectangleSum(sI, eI, j, eJ, rectangle);
            
            long tmp = a * b * c;
            
            if(max < tmp) {
                max = tmp;
            }
        }
        
        return max;
    }
    
    private static long getRectangleSum(int sI, int eI, int sJ, int eJ, int[][] rectangle) {
        long sum = 0;
        
        for(int i = sI; i < eI; i++) {
            for(int j = sJ; j < eJ; j++) {
                sum += rectangle[i][j];
            }
        }
        
        return sum;
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 

 

메서드로 빼도 딱히 많은 이점이 있는 것 같지는 않다. 이런 문제는 극혐이나 알고리즘 테스트에 나온다면 풀어야하니 어쩔 수 없이 연습을 해야겠지...ㅠㅠ

 

 

문제링크 : https://www.acmicpc.net/problem/7453

 

7453번: 합이 0인 네 정수

문제 정수로 이루어진 크기가 같은 배열 A, B, C, D가 있다. A[a], B[b], C[c], D[d]의 합이 0인 (a, b, c, d) 쌍의 개수를 구하는 프로그램을 작성하시오. 입력 첫째 줄에 배열의 크기 n (1 ≤ n ≤ 4000)이 주어진다. 다음 n개 줄에는 A, B, C, D에 포함되는 정수가 공백으로 구분되어져서 주어진다. 배열에 들어있는 정수의 절댓값은 최대 228이다. 출력 합이 0이 되는 쌍의 개수를 출력한다. 예제 입력 1 복

www.acmicpc.net

이 문제에서 모든 경우의 수를 다 구해본다면, 그 때의 시간복잡도는 O(N^4)이다. N의 최댓값이 4000밖에 되지 않기 때문에 그렇게 푸는 것도 가능할 것 같긴 했으나(해보진 않음;;) 더 효율적인 방법을 알기에 그렇게 하지 않았다.

 

1. A와 B의 합, C와 D의 합에 대한 모든 경우의 수를 각각 subsetAB, subsetCD라는 새로운 배열에 저장한다. 이 때 각각의 subset의 크기는 N * N이 될 것이다.

2. 두 subset을 오름차순으로 정렬한다.

3. 두 subset의 합이 0일 때의 경우의 수를 센다. 이 때 투포인터 알고리즘을 사용한다.

 

투포인터 알고리즘에 대해 자세히 설명하면,

일단 left = 0, right = N * N - 1(subset의 가장 마지막 인덱스)로 초기화한다.

left는 subsetAB의 인덱스를 가리킬 것이고, right는 subsetCD의 인덱스를 가리킬 것이다.

 

현재 각각의 subset은 오름차순으로 정렬되어있다. subsetAB[left] + subsetCD[right]의 값이 0보다 작다면 어떻게 해야할까? left를 증가시켜야한다. 현재의 subsetCD는 가장 큰값이므로 더 증가시킬수가없다. 반대의경우에는 right를 감소시키면된다. 이런식의 탐색을 통해 훨씬 적은 횟수의 비교를 통해 네 배열의 합이 0인 모든경우의수를 구할 수있다. 투포인터를 사용했을 시 시간복잡도는 O(N^2)이다. 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        
        int[] a = new int[n];
        int[] b = new int[n];
        int[] c = new int[n];
        int[] d = new int[n];
        
        for(int i = 0; i < n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            a[i] = Integer.parseInt(st.nextToken());
            b[i] = Integer.parseInt(st.nextToken());
            c[i] = Integer.parseInt(st.nextToken());
            d[i] = Integer.parseInt(st.nextToken());
        }
        
        int[] subsetAB = getSubset(n, a, b);
        int[] subsetCD = getSubset(n, c, d);
        
        Arrays.sort(subsetAB);
        Arrays.sort(subsetCD);
        
        int left = 0;
        int right = n * n - 1;
        long cnt = 0;
        
        while(left < n * n && right >= 0) {
            int leftVal = subsetAB[left];
            int rightVal = subsetCD[right];
            int tmp = leftVal + rightVal;
            
            if(tmp < 0) {
                while(++left < n * n && subsetAB[left] == leftVal) {
                    // do nothing
                }
            } else if(tmp > 0) {
                while(--right >= 0 && subsetCD[right] == rightVal) {
                    // do nothing
                }
            } else {
                long leftCnt = 1;
                long rightCnt = 1;
                
                while(++left < n * n && subsetAB[left] == leftVal) {
                    leftCnt++;
                }
                
                while(--right >= 0 && subsetCD[right] == rightVal) {
                    rightCnt++;
                }
                
                cnt += leftCnt * rightCnt;
            }
        }
        
        System.out.println(cnt);
    }
 
    private static int[] getSubset(int n, int[] a, int[] b) {
        int[] subset = new int[n * n];
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                subset[i * n + j] = a[i] + b[j];
            }
        }
        
        return subset;
    }
}
 
cs

 

문제링크 : https://www.acmicpc.net/problem/1912

 

1912번: 연속합

첫째 줄에 정수 n(1 ≤ n ≤ 100,000)이 주어지고 둘째 줄에는 n개의 정수로 이루어진 수열이 주어진다. 수는 -1,000보다 크거나 같고, 1,000보다 작거나 같은 정수이다.

www.acmicpc.net

이 문제를 보고 '그냥 모든 경우의 수를 다 구하면 안 되나?'란 생각이 들었다. 이 때의 시간 복잡도는 O(n^2)인데 n의 최댓값이 100,000이라 이 정도면 가능하지 싶었다. 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        int[] seq = new int[n];
        int[] sums = new int[n];
        int max = Integer.MIN_VALUE;
        int sum = 0;
        
        for(int i = 0; i < n; i++) {
            seq[i] = Integer.parseInt(st.nextToken());
            sum += seq[i];
            sums[i] = sum;
            
            if(max < sums[i]) {
                max = sums[i];
            } 
        }
        
        for(int i = 1; i < n; i++) {
            for(int j = 0; j < i; j++) {
                int tmp = sums[i] - sums[j];
                
                if(max < tmp) {
                    max = tmp;
                }
            }
        }
        
        System.out.println(max);
    }
}
 
 
 

 

맞긴 맞았는데 상위권 풀이와 비교해보니 시간 차이가 너무 많이났다-_- 이거이거 잘못 풀었구나...ㅠㅠ

 

제대로 푼 방법에 대한 설명을 하겠다. 0부터 n - 1까지 i를 증가시키면서 그 시점에서의 최댓값을 구한다. 그 시점에서의 최댓값이란, i = a라고할 때, 0부터 a까지의 수열 중에서 a가 선택되었을 때의 가장 큰 연속합을 의미한다. 아래의 수열을 예를 들어 설명해보겠다.

 

10 -4 3 1 5 6 -35 12 21 -1

 

i=0일 때 -> 10

i=1일 때 -> i=0일때의 최댓값이 양수이므로 그 값을 더한 값이 최대값 -> 6

i=2일 때 -> i=1일때의 최댓값이 양수이므로 그 값을 더한 값이 최대값 -> 9

i=3일 때 -> i=2일때의 최댓값이 양수이므로 그 값을 더한 값이 최대값 -> 10

i=4일 때 -> i=3일때의 최댓값이 양수이므로 그 값을 더한 값이 최대값 -> 15

i=5일 때 -> i=4일때의 최댓값이 양수이므로 그 값을 더한 값이 최대값 -> 21

i=6일 때 -> i=5일때의 최댓값이 양수이므로 그 값을 더한 값이 최대값 -> -14

i=7일 때 -> i=6일때의 최댓값이 음수이므로 더하지 않는 것이 최대값 -> 12

i=8일 때 -> i=7일때의 최댓값이 양수이므로 그 값을 더한 값이 최대값 -> 33

i=9일 때 -> i=8일때의 최댓값이 양수이므로 그 값을 더한 값이 최대값 -> 32

 

따라서 최댓값이 33임을 알 수 있다. 이 때의 시간 복잡도는 O(n)이다. 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        int[] seq = new int[n];
        seq[0= Integer.parseInt(st.nextToken());
        int maxCandidate = seq[0];
        int max = maxCandidate;
        
        for(int i = 1; i < n; i++) {
            seq[i] = Integer.parseInt(st.nextToken());
            
            if(maxCandidate > 0) {
                maxCandidate = seq[i] + maxCandidate;
            } else {
                maxCandidate = seq[i];
            }
            
            if(max < maxCandidate) {
                max = maxCandidate;
            }
        }
        
        System.out.println(max);
    }
}
 
 

 

문제링크 : https://www.acmicpc.net/problem/11662

 

11662번: 민호와 강호

민호와 강호가 2차원 좌표 평면 위에 있다. 민호는 점 A(Ax, Ay)에서 점 B(Bx, By)를 향해 걸어가고 있고, 강호는 점 C(Cx, Cy)에서 점 D(Dx, Dy)를 향해 걸어가고 있다. 민호와 강호는 동시에 출발하고, 민호가 점 B에 도착하는 순간 강호도 점 D에 도착한다. 또, 두 사람은 항상 일정한 속도로 걸어간다. 두 사람의 거리가 가장 가까울 때, 거리를 구하는 프로그램을 작성하시오. 두 점 (x1, y1), (x2, y2)사이의 거리

www.acmicpc.net

민호가 A -> B로, 강호가 C -> D로 걸어가는데 둘이 도착하는 시간이 같다. 이 때 민호와 강호가 가장 가까울 때의 거리를 구하는 문제이다. 별로 어려운 문제는 아니다. 민호가 가는 거리와 강호가 가는 거리를 일정 간격(interval)로 쪼개준 다음에 조금씩 이동시키면서 매번 거리를 구한 후 그 중에 가장 작은 값을 구하면 된다. 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        double aX1 = Double.parseDouble(st.nextToken());
        double aY1 = Double.parseDouble(st.nextToken());
        double aX2 = Double.parseDouble(st.nextToken());
        double aY2 = Double.parseDouble(st.nextToken());
        double cX1 = Double.parseDouble(st.nextToken());
        double cY1 = Double.parseDouble(st.nextToken());
        double cX2 = Double.parseDouble(st.nextToken());
        double cY2 = Double.parseDouble(st.nextToken());
        
        int interval = 1000000;
        
        double aDX = (aX2 - aX1) / interval;
        double aDY = (aY2 - aY1) / interval;
        double cDX = (cX2 - cX1) / interval;
        double cDY = (cY2 - cY1) / interval;
        
        double min = getDistance(aX1, aY1, cX1, cY1);
        
        for(int i = 1; i <= interval; i++) {
            double tmp = getDistance(aX1 + aDX * i, aY1 + aDY * i, cX1 + cDX * i, cY1 + cDY * i);
            
            if(tmp < min) {
                min = tmp;
            }
        }
        
        System.out.println(min);
    }
    
    private static double getDistance(double x1, double y1, double x2, double y2) {
        return Math.sqrt(Math.pow(x2 - x1, 2+ Math.pow(y2 - y1, 2));
    }
}
 
 

 

문제를 맞추고 난 후, 여느때와 다름없이 상위권 풀이와 비교해보니 어째 시간이 좀 길게 나오기도 하고, 푸는 방법도 달랐다. 구글링을 좀 해보니 '삼분탐색'이라고 하더라. 삼분탐색에 대한 자세한 설명은 아래 링크를 참고하면 좋다.

https://m.blog.naver.com/PostView.nhn?blogId=kks227&logNo=221432986308&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

삼분 탐색(Ternary Search)

안녕하세요. 이번에 들고 온 주제는 parametric search의 방법 중 하나인 삼분 탐색(ternary search)입니다...

blog.naver.com

근데 삼분탐색은, 최솟값을 구하는 경우 그 함수(여기선 i를 변수로 하는 함수라고 생각하면 된다)가 아래로 볼록할 때만 사용할 수 있다고 하더라. 근데 이게 아래로 볼록한 함수인지 어떻게 확신할 수 있지? 나는 그냥 수학적으로 직접 증명해보기로 했다. 고등학교 때 배웠던 미분이 약간 필요하다. 수학이라면 진절머리가 나는 사람이라면 그냥 스킵해도 좋다.

 

깔끔한 글씨로 계산해보려 했으나 실패...

일단 수학 시간이 아니므로 간단하게만 설명하겠다. f'(i) 의 분모는 항상 양수일 것이다.

분자는 (B^2 + D^2) * i + AB + CD인데, i의 계수는 항상 양수인 일차함수이므로 이 함수는 f'(i) 값이 음수에서 양수로 바뀌는, 즉 감소함수에서 증가함수로 바뀌는 함수라는 것을 알 수 있다. 아래로 볼록이라는 뜻이다. '이 문제에서 i는 0보다 크거나 개발자가 정한 최대값(interval)보다 작은 정수인데, 극소값이 존재하는 범위가 이 범위 밖에 벗어나면 그냥 증가함수나 감소함수가 되는 거 아닌가?!'라고 할 수도 있으나 삼분탐색은 그런 경우에도 사용 가능하다. 이분탐색보다 넓은 범위에서 사용 가능하다고 생각하면 될 것 같다. B^2 + D^2 = 0인 경우도 증가 혹은 감소함수가 될테니 설명 가능하다. (물론 AB+CD=0일 경우, 상수함수가 될텐데 이 경우에도 삼분탐색이 가능하긴 하다. 자세한 설명은 생략) 

 

이렇게 아래로 볼록이라는 걸 확실히 알 수 있을 때 삼분탐색을 이용해야하는 거 아닐까? 근데 생각해보자. 저렇게까지 계산했는데 굳이 삼분탐색을 이용해야할까?; 이왕 미분한거 극값이 나오는 i를 구해버리면 되지 않는가?!? 그래서 위의 계산식에 보면, f'(i) = 0일 때의 i값을 계산해놓았다. 분모가 0일때는 NaN이고, i가 범위를 벗어나는 경우도 있을 것이다. 그 경우에는 최소 값이 그래프의 양 끝 값 중 하나일 수밖에 없다. 

 

삼분탐색으로 푼 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        double aX = Double.parseDouble(st.nextToken());
        double aY = Double.parseDouble(st.nextToken());
        double bX = Double.parseDouble(st.nextToken());
        double bY = Double.parseDouble(st.nextToken());
        double cX = Double.parseDouble(st.nextToken());
        double cY = Double.parseDouble(st.nextToken());
        double dX = Double.parseDouble(st.nextToken());
        double dY = Double.parseDouble(st.nextToken());
        
        int interval = 1000000;
        
        double aDX = (bX - aX) / interval;
        double aDY = (bY - aY) / interval;
        double cDX = (dX - cX) / interval;
        double cDY = (dY - cY) / interval;
        
        int lo = 0;
        int hi = interval;
        
        while(hi - lo >= 3) {
            int p = (2 * lo + hi) / 3;
            int q = (lo + 2 * hi) / 3;
            
            double pVal = getDistance(aX + aDX * p, aY + aDY * p, cX + cDX * p, cY + cDY * p);
            double qVal = getDistance(aX + aDX * q, aY + aDY * q, cX + cDX * q, cY + cDY * q);
            
            if(pVal < qVal) {
                hi = q - 1;
            } else {
                lo = p + 1;
            }
        }
        
        double min = getDistance(aX + aDX * hi, aY + aDY * hi, cX + cDX * hi, cY + cDY * hi);
        
        for(int i = lo; i < hi; i++) {
            double temp = getDistance(aX + aDX * i, aY + aDY * i, cX + cDX * i, cY + cDY * i);
            
            if(temp < min) {
                min = temp;
            }
        }
        
        System.out.println(min);
    }
 
    private static double getDistance(double x1, double y1, double x2, double y2) {
        return Math.sqrt(Math.pow(x2 - x1, 2+ Math.pow(y2 - y1, 2));
    }
}
 
 

 

미분을 통한 극솟값 계산을 통해 푼 풀이는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        double aX1 = Double.parseDouble(st.nextToken());
        double aY1 = Double.parseDouble(st.nextToken());
        double aX2 = Double.parseDouble(st.nextToken());
        double aY2 = Double.parseDouble(st.nextToken());
        double cX1 = Double.parseDouble(st.nextToken());
        double cY1 = Double.parseDouble(st.nextToken());
        double cX2 = Double.parseDouble(st.nextToken());
        double cY2 = Double.parseDouble(st.nextToken());
        
        int interval = 1000000;
        
        double aDX = (aX2 - aX1) / interval;
        double aDY = (aY2 - aY1) / interval;
        double cDX = (cX2 - cX1) / interval;
        double cDY = (cY2 - cY1) / interval;
        
        double i = -1 * ((aX1 - cX1) * (aDX - cDX) + (aY1 - cY1) * (aDY - cDY)) / (Math.pow(aDX - cDX, 2+ Math.pow(aDY - cDY, 2));
        
        if(!Double.isNaN(i) && i < interval && i > 0) {
            double min = getDistance(aX1 + aDX * i, aY1 + aDY * i, cX1 + cDX * i, cY1 + cDY * i);
            System.out.println(min);
        } else {
            double min1 = getDistance(aX1, aY1, cX1, cY1);
            double min2 = getDistance(aX2, aY2, cX2, cY2);
            
            System.out.println(Math.min(min1, min2));
        }
    }
    
    private static double getDistance(double x1, double y1, double x2, double y2) {
        return Math.sqrt(Math.pow(x2 - x1, 2+ Math.pow(y2 - y1, 2));
    }
}
 
 

개인적으로 필자는 1번 풀이 혹은 3번 풀이가 좋은 풀이라고 본다. 즉, 이 문제를 굳이 삼분탐색으로 풀 이유가 없는 것 같다. 이유는, 아래로 볼록인 게 확실하지 않은 상황에서는 삼분탐색을 해선 안된다. 아래로 볼록인 걸 증명하기 위해 수학적 계산을 하면, 이 문제의 경우 미분 가능하기 때문에 그냥 극값을 구해버리는 게 낫다. 시간복잡도 측면에서 압도적으로 유리하기 때문이다. 필자가 남겨놓은 삼분탐색 참고를 위한 링크 글에서도 나오지만, 삼분탐색은 함수가 미분 불가능할 때만 써야할 것 같다.

 

문제링크 : https://www.acmicpc.net/problem/10816

 

10816번: 숫자 카드 2

첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이가 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다. 셋째 줄에는 M(1 ≤ M ≤ 500,000)이 주어진다. 넷째 줄에는 상근이가 몇 개 가지고 있는 숫자 카드인지 구해야 할 M개의 정수가 주어지며, 이 수는 공백으로 구분되어져 있다. 이수도 -10,00

www.acmicpc.net

문제를 보자마자 전형적인 이분탐색문제라고 생각했다. 그렇게 생각하고 문제를 풀었으나, 시간초과...-_- 다른 언어에서도 그런지는 모르겠으나 적어도 자바에서는 이분탐색으로 이 문제를 풀면 시간초과가 뜬다. 이분탐색으로 푼 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());
        int[] cards = new int[n];
        
        for(int i = 0; i < n; i++) {
            cards[i] = Integer.parseInt(st.nextToken());
        }
        
        Arrays.sort(cards);
        
        int m = Integer.parseInt(br.readLine());
        
        StringBuffer sb = new StringBuffer();
        st = new StringTokenizer(br.readLine());
        
        for(int i = 0; i < m; i++) {
            int x = Integer.parseInt(st.nextToken());
            int cnt = getCnt(cards, x, n);
            sb.append(cnt + " ");
        }
        
        System.out.println(sb);
    }
 
    private static int getCnt(int[] cards, int x, int n) {
        int left = 0;
        int right = n - 1;
        int cnt = 0;
        
        while(left <= right) {
            int mid = (left + right) / 2;
            
            if(x < cards[mid]) {
                right = mid - 1;
            } else if(x > cards[mid]) {
                left = mid + 1;
            } else {
                cnt = 1;
                int tmp = mid;
                
                while(--tmp >= 0 && cards[tmp] == x) {
                    cnt++;
                }
                
                while(++mid < n && cards[mid] == x) {
                    cnt++;
                }
                
                break;
            }
        }
        
        return cnt;
    }
}
 
 

 

그럼 대체 어떻게 풀어야한단 말인가... 고뇌를 하던 와중, 숫자의 범위가 -10,000,000 ~ 10,000,000이라는 게 눈에 들어왔다. 그럼 20,000,001개의 수이고 int 는 4 byte니까 20,000,001 X 4 = 80,000,004 -> 약 80MB~!!!! 생각보다 배열이 크지 않구만~! 배열에 모든 수의 개수를 저장하면 되겠다는 생각이 들었다. 코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());
        int[] cnt = new int[20_000_001];
        
        for(int i = 0; i < n; i++) {
            cnt[Integer.parseInt(st.nextToken()) + 10_000_000]++;
        }
        
        int m = Integer.parseInt(br.readLine());
        st = new StringTokenizer(br.readLine());
        StringBuffer sb = new StringBuffer();
        
        for(int i = 0; i < m; i++) {
            sb.append(cnt[Integer.parseInt(st.nextToken()) + 10_000_000] + " ");
        }
        
        System.out.println(sb);
    }
}
 
 

 

문제를 만든 사람이 어떤 의도로 만든 문제인지는 모르겠으나, 현재 이 문제는 메모이제이션으로 푸는게 가장 효과적이다. 근데 수의 범위가 더 커진다면, 메모리를 너무 많이 사용해야함으로 이분 탐색이 더 나은 알고리즘이 될지도 모르겠다.

+ Recent posts