반응형

* 핵심

이진 탐색을 이용하여 조건에 맞는 최적의 x를 구하는 문제


* 문제

문제

집에서 시간을 보내던 오영식은 박성원의 부름을 받고 급히 달려왔다. 박성원이 캠프 때 쓸 N개의 랜선을 만들어야 하는데 너무 바빠서 영식이에게 도움을 청했다.

이미 오영식은 자체적으로 K개의 랜선을 가지고 있다. 그러나 K개의 랜선은 길이가 제각각이다. 박성원은 랜선을 모두 N개의 같은 길이의 랜선으로 만들고 싶었기 때문에 K개의 랜선을 잘라서 만들어야 한다. 예를 들어 300cm 짜리 랜선에서 140cm 짜리 랜선을 두 개 잘라내면 20cm 은 버려야 한다.(이미 자른 랜선은 붙일 수 없다.)

편의를 위해 랜선을 자르거나 만들 때 손실되는 길이는 없다고 가정하며, 기존의 K개의 랜선으로 N개의 랜선을 만들 수 없는 경우는 없다고 가정하자. 그리고 자를 때는 항상 센티미터 단위로 정수길이만큼 자른다고 가정하자. 이때 만들 수 있는 최대 랜선의 길이를 구하는 프로그램을 작성하시오.

입력

첫째 줄에는 오영식이 이미 가지고 있는 랜선의 개수 K, 그리고 필요한 랜선의 개수 N이 입력된다. K는 1이상 10,000이하의 정수이고, N은 1이상 1,000,000이하의 정수이다. 그리고 항상 K ≦ N 이다. 그 후 K줄에 걸쳐 이미 가지고 있는 각 랜선의 길이가 센티미터 단위의 정수로 입력된다. 랜선의 길이는 231-1보다 작거나 같은 자연수이다.

출력

첫째 줄에 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
33
34
35
36
37
import java.io.*;
import java.util.*;
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stk = new StringTokenizer(br.readLine());
 
        int k = Integer.parseInt(stk.nextToken());
        int n = Integer.parseInt(stk.nextToken());
        int[] line = new int[k];
        long min = 1;
        long max = 0;
 
        for (int i = 0; i < k; i++) {
            line[i] = Integer.parseInt(br.readLine());
            max = Math.max(max, line[i]);
        }
        long ans = 0;
        while (min <= max) {
            long mid = (min + max) / 2;
            long sum = 0;
            for (int i = 0; i < k; i++) {
                if (line[i] >= mid) {
                    sum += line[i] / mid;   //mid로 나눈 몫 저장(랜선의 갯수)
                }
            }
            if (sum >= n) {     //목표보다 많은 랜선이 만들어진 경우
                min = mid + 1;  //mid 값을 높여서 랜선의 개수를 줄임
                ans = Math.max(ans, mid);
            } else {            //목표보다 적은 랜선이 만들어진 경우
                max = mid - 1;  //mid 값을 낮춰서 랜선의 개수를 늘림
            }
        }
        System.out.println(ans);
    }
}
cs


반응형
반응형

* 핵심

각 캐릭터의 레벨을 이분탐색을 통해 k를 넘지 않는 최적의 해를 구하는 문제


* 문제 

문제

성권이는 Heroes of the Storm 프로게이머 지망생이다.

이 게임에는 총 N개의 캐릭터가 있다. 그리고 현재 각 캐릭터의 레벨은 Xi이다. 성권이는 앞으로 게임이 끝날 때까지, 레벨을 최대 총합 K만큼 올릴 수 있다.

팀 목표레벨 T =min(Xi) (1 ≤ i ≤ N)라고 정의하면, 게임이 끝날 때까지 성권이가 달성할 수 있는 최대 팀 목표레벨 T는 무엇인가?

예를 들어, N = 3, X1= 10, X2= 20, X3= 15이고 K = 10일 때, X1을 7만큼 올리고 X3을 2만큼 올리면 최소 레벨 Xi는 17이 된다. 따라서 팀 목표레벨 T는 17이다. 이 경우처럼 레벨을 총합 K보다 적게 올릴 수도 있다.

입력

첫째 줄에는 캐릭터의 개수 N, 올릴 수 있는 레벨 총합 K가 주어진다. (1 ≤ N ≤1,000,000, 1 ≤ K ≤ 1,000,000,000)

다음 N개의 줄에는 현재 각 캐릭터의 레벨이 X1X2, X3, ... , Xn 으로 주어진다. (1 ≤ Xi ≤ 1,000,000,000)

출력

가능한 최대 팀 목표레벨 T를 출력한다.


* 소스 코드

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
import java.io.*;
import java.util.*;
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stk = new StringTokenizer(br.readLine());
        StringBuilder sb = new StringBuilder();
 
        int n = Integer.parseInt(stk.nextToken());
        int k = Integer.parseInt(stk.nextToken());
        int[] team = new int[n];
        long min = Integer.MAX_VALUE;
        long max = Integer.MAX_VALUE;
 
        for (int i = 0; i < n; i++) {
            team[i] = Integer.parseInt(br.readLine());
            min = Math.min(min, team[i]);
        }
        long ans = 0;
        while (min <= max) {
            long mid = (min + max) / 2;
            long sum = 0;
            for (int i = 0; i < n; i++) {
                if (mid >= team[i]) {
                    sum += mid - team[i];
                }
            }
            if (k >= sum) {
                min = mid + 1;
                ans = Math.max(ans, mid);
            } else {
                max = mid - 1;
            }
        }
        System.out.println(ans);
    }
}
cs


반응형
반응형

* Binary Search(이분 탐색)

이분 탐색은 정렬되어 있는 배열에서 값을 어떤 값(x)을 찾을때 사용할 수 있는 알고리즘이다.

기본 방법은, 정렬되어 있는 left와 right를 지정하여 배열의 중간 값(mid)을 구한다.

mid와 x를 비교했을 때, x가 더 크다면 left 값을 mid+1로 설정한다. (mid값 보다 오른쪽 검색)

mid와 x를 비교했을 때, mid가 더 크다면 right 값을 mid-1로 설정한다. (mid값 보다 왼쪽 검색)

위 과정을 반복하여 만족하는 값을 찾는 탐색 방식이다.


* 시간 복잡도

처음부터 끝까지 원하는 값을 찾을 때까지 탐색을 계속하는 순차 탐색은 Worst Case일 때 O(n)이라는 Time Complexity를 보여준다. 

10만개의 데이터가 있다면 무려 10만번을 탐색해야 하는 것이다. 

그러나, Binary Search는 탐색 대상을 절반씩 계속해서 줄여나가기 때문에, Worst Case일 때에도 탐색의 횟수가 log2n 이 되므로 10만개의 데이터가 있다고 하더라도 약 16번 정도로 탐색을 끝낼 수 있다. 

즉, 이분 탐색의 시간 복잡도는 O(logn)이다.

반응형
반응형

* 핵심

Divide and Conquer 사용하여 시간 복잡도 줄이는 방식

10^11 = 10 * 10^5 * 10*5 이고, 10^5 = 10 * 10^2 * 10^2 라는 개념을 이용


* 문제

문제

자연수 A를 B번 곱한 수를 알고 싶다. 단 구하려는 수가 매우 커질 수 있으므로 이를 C로 나눈 나머지를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 A, B, C가 빈 칸을 사이에 두고 순서대로 주어진다. A, B, C는 모두 2,147,483,647 이하의 자연수이다.

출력

첫째 줄에 A를 B번 곱한 수를 C로 나눈 나머지를 출력한다.


* 소스 코드

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
import java.io.*;
import java.util.*;
 
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stk = new StringTokenizer(br.readLine());
 
        int a = Integer.parseInt(stk.nextToken());
        int b = Integer.parseInt(stk.nextToken());
        int c = Integer.parseInt(stk.nextToken());
 
        long ans = DivideConquer(a, b, c);
        System.out.println(ans);
    }
 
    public static long DivideConquer(int a, int b, int c) {
        if (b == 0return 1;       //종료 조건
        if (b % 2 == 1) {           //지수가 홀수인 경우
            return DivideConquer(a, b - 1, c) * a % c;
        } else {                    //지수가 홀수인 경우
            long v = DivideConquer(a, b / 2, c) % c;
            return v * v % c;
        }
    }
}
cs


반응형
반응형

* 분할 정복(Divide and Conquer)

분할 정복이란 문제를 소문제로 변경하여 소문제를 해결하여 큰 문제를 해결하는 방식이다.

1. 문제 사례를 하나 이상의 작은 사례로 분할(Divide)한다.

2. 작은 사례들을 각각 정복(Conquer)한다. 작은 사례가 충분히 작지 않은 이상 재귀를 사용한다.

3. 필요하다면, 작은 사례에 대한 해답을 통합(Combine)하여 원래 사례의 해답을 구한다.


* 분할 정복의 특징

장점 : 큰 문제를 비교적 작은 문제로 변환하고, 재귀로 구현하기 때문에 코드가 비교적 간단한다.

단점 : 재귀를 사용하기 때문에 StackOverflow가 발생할 수 있다.


* 예시

1
2
3
4
5
6
7
8
9
public static long DivideConquer(int a, int b, int c) {
    if (b == 0return 1;       //종료 조건
    if (b % 2 == 1) {           //지수가 홀수인 경우
        return DivideConquer(a, b - 1, c) * a % c;
    } else {                    //지수가 홀수인 경우
        long v = DivideConquer(a, b / 2, c) % c;
        return v * v % c;
    }
}
cs

백준 1629 :: 곱셈 풀이

반응형
반응형

* Bitmast(비트마스크)

정수의 이진수 표현을 자료 구조로 쓰는 기법

특징 : 0과 1을 사용하여 선택/미선택을 판별할 수 있음

장점 : 간결한 코드, 빠른 연산 속도, 적은 메모리 사용

단점 : 순서가 필요한 경우 or 선택/미선택으로 해결되지 않는 문제의 경우 사용 불가


* Bit 연산자

- AND 연산 (기호 : a & b ) : 둘 다 1인 경우에만 결과가 1이 된다.

  ex) 111 & 101 = 101

  ex) 101 & 100 = 100


- OR 연산 (기호 : a | b ) : 둘중에 하나만 값이 1 이면 결과는 1 이 된다.

  ex) 101 | 100 = 101

  ex) 011 | 100 = 111


- XOR 연산 (기호 : a ^ b ) : 둘이 같은 값 (11 or 00) 이면 0 이 되고, 둘이 다른 값 (10 or 01) 이면 1 이 된다.

  ex) 101 | 111 = 010

  ex) 1011 | 0100 = 1111


- NOT 연산 (기호 : ~a ) : 두개의 비트 값에 대한 연산이 아닌 단일 비트값에 대하여 적용하는 연산이다. 각 자릿수를 반대값으로 뒤집는다. 즉, 0 인경우 1로 바꾸고, 1 인 경우 0 으로 바꾼다.

  ex) ~(1011) = 0100

  ex) ~(0011) = 1100


- LEFT SHIFT 연산 (기호 : a << b ) : 값 a 의 모든 각 자릿수의 비트를 좌측으로 b 번 만큼 민다. (우측에는 b개의 0 이 붙는다)

  ex) 1 << 5 = 100000

  ex) 1011 << 2 = 101100


- RIGHT SHIFT 연산 (기호 : a >> b ) : 값 a 의 모든 각 자릿수의 비트를 우측으로 b 번 만큼 민다. (좌측에는 b개의 0 이 붙는다)

  ex) 1101101 >> 2 = 11011

  ex) 110010  >> 5 = 1


* 사용 방법

1의 경우 선택, 0의 경우 미선택이라고 정의했을 때, 8개중 2개를 확인하는 방법

(1 << 0) & 255 != 0    >> 11111111 & 00000001    >> 첫 번째 선택 여부 확인

(1 << 1) & 255 != 0    >> 11111111 & 00000010    >> 두 번째 선택 여부 확인

(1 << 2) & 255 != 0    >> 11111111 & 00000100    >> 세 번째 선택 여부 확인


와 같이 비트연산을 통해 선택 여부를 확인할 수 있다.

반응형
반응형

* 핵심

String에 += 사용 시 StringBuilder로 변환하여 Append 한 후에 toString 으로 변환 >> 많은 시간 소요

추가되는 문자열에 len/2 만큼만 반복하여 Backtracking 실행 >> 메모리 초과 에러 방지


* 문제

문제

숫자 1, 2, 3으로만 이루어지는 수열이 있다. 임의의 길이의 인접한 두 개의 부분 수열이 동일한 것이 있으면, 그 수열을 나쁜 수열이라고 부른다. 그렇지 않은 수열은 좋은 수열이다.

다음은 나쁜 수열의 예이다.

  • 33
  • 32121323
  • 123123213

다음은 좋은 수열의 예이다.

  • 2
  • 32
  • 32123
  • 1232123

길이가 N인 좋은 수열들을 N자리의 정수로 보아 그중 가장 작은 수를 나타내는 수열을 구하는 프로그램을 작성하라. 예를 들면, 1213121과 2123212는 모두 좋은 수열이지만 그 중에서 작은 수를 나타내는 수열 1213121이다.

입력

입력은 숫자 N하나로 이루어진다. N은 1 이상 80 이하이다.

출력

첫 번째 줄에 1, 2, 3으로만 이루어져 있는 길이가 N인 좋은 수열들 중에서 가장 작은 수를 나타내는 수열만 출력한다. 수열을 이루는 1, 2, 3들 사이에는 빈칸을 두지 않는다.


* 소스 코드

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
import java.io.*;
import java.util.*;
 
public class Main {
    public static int n;
    public static String res = "";
    public static StringBuilder sb = new StringBuilder();
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stk = new StringTokenizer(br.readLine());
 
        n = Integer.parseInt(stk.nextToken());
        check("1");
    }
 
    public static void check(String s) {
        if (s.length() == n) {
            System.out.println(s);
            System.exit(0);
        } else {
            for (int i = 1; i <= 3; i++) {
                if (backtracking(s + i)) check(s+i);
            }
        }
    }
 
    public static boolean backtracking(String s) {
        int len = s.length();
        for (int i = 1; i <= len / 2; i++) {
            String front = s.substring(len - i * 2, len - i);
            String back = s.substring(len - i, len);
            if (front.equals(back)) return false;
        }
        return true;
    }
}
cs


반응형
반응형

* 핵심

Backtracking 문제로 접근하여 n명이 있을 때, n/2 명의 팀을 하나의 팀으로 선정하는 작업을 우선 


* 문제

문제

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

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

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

i\j1234
1 123
24 56
371 2
4345 

예를 들어, 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
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
import java.io.*;
import java.util.*;
 
public class Main {
    public static int n;
    public static int min = Integer.MAX_VALUE;
    public static int[][] map;
    public static boolean[] visited;        //방문
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stk = new StringTokenizer(br.readLine());
 
        n = Integer.parseInt(stk.nextToken());
        map = new int[n][n];
        visited = new boolean[n];
        for (int i = 0; i < n; i++) {
            stk = new StringTokenizer(br.readLine());
            for (int j = 0; j < n; j++) {
                map[i][j] = Integer.parseInt(stk.nextToken());
            }
        }
        backtracking(00);
        System.out.println(min);
    }
 
    public static void backtracking(int start, int depth) {
        if (n / 2 == depth) {
            int t1 = 0, t2 = 0;
            for (int i = 0; i < n; i++) {
                for (int j = i+1; j < n; j++) {
                    if (visited[i] && visited[j]) {
                        t1 += map[i][j] + map[j][i];
                    }
                    if (!(visited[i] || visited[j])) {
                        t2 += map[i][j] + map[j][i];
                    }
                }
            }
            min = Math.min(min, Math.abs(t1 - t2));
            return;
        } else {
            for (int i = start; i < n; i++) {
                if (!visited[i]) {
                    visited[i] = true;
                    backtracking(i, depth + 1);
                    visited[i] = false;
                }
            }
        }
    }
}
cs


반응형