CS/ProblemSolving

2 - 4. Back Tracking 예제 (3671번: 산업 스파이의 편지)

exponential-e 2019. 3. 16. 16:29

백준 3671번(산업 스파이의 편지)



저번에 잠깐 설명했던 백트래킹 문제를 하나 가져왔습니다.

(열심히 썼으니... 아직 잘 모르시겠다면, 여기를 참고해주세요.)




소수를 이용하네요. 범위는 7자리니까 1 ~ 9,999,999까지 나옵니다.

테스트 케이스가 최대 200개이고, 따라서 그냥 막 접근하면 시간초과가 나겠죠.


우선 소수를 빠르게 구할 수 있는 에라토스테네스의 체(Eratosthenes's sieve) 알고리즘부터 공부해보시는게 좋습니다.

수학 관련 알고리즘을 다룰건 아니니까 자세한 설명은 생략하겠습니다. 아래 링크를 참고해주세요.

(wiki: 에라토스테네스의 체)



우선적으로 테스트케이스를 여러번 돌려야하니까 미리 반복문 밖에서 소수를 최댓값(9,999,999)까지 구해둡니다.

그리고 다음으로 백트래킹을 이용해서 탐색하는데, 숫자가 몇자리 주어지고 이를 통해서 만들 수 있는 소수의 수를 찾아야합니다.

종이는 중복으로 사용은 불가하지만, 가지고 있는 것 중에서 제외시키고 구성하는 것은 가능합니다.


이정도만 확인하고 넘어가면 될 것 같습니다.

그리고 우리는 정해진 숫자로 재배치만 하면 되는 것이니, 1 ~ 9,999,999까지 하나씩 반복문을 돌려 볼 필요도 없겠네요.



결국 지난번 설명했던 'N과 M'이라는 문제와 매우 유사합니다.

다른점은 주어진 숫자를 모두 사용할 필요가 없다는 것, 그리고 소수인지 판별해야하는 것, 두 가지네요.

그 외에는 진짜 너무 다 똑같기 때문에 따로 드릴 설명은 없을 것 같아요.

(이전 포스팅 보시고 그래도 이해 안되신다면, 댓글 달아주세요. 같이 고민해보겠습니다.)


prime: 인덱스에 해당하는 수가 소수라면 true 값을 가집니다.

used: 주어진 테스트 케이스의 i번째 숫자가 사용 중인지 표시 해줍니다. (즉, 34218의 i == 1 이면 4가 됩니다.)

list: 중복 방문을 위해 이미 찾은 소수는 소수 배열에서 false로 바꿔주는데요. 

이 소수를 탐색이 끝난 후 다시 true로 바꾸기 위해 사용합니다.



3671번 전체 코드

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
package back_tracking;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.LinkedList;
 
/**
 * 
 *     @author minchoba
 *    백준 3671번: 산업 스파이의 편지
 *
 *    @see https://www.acmicpc.net/problem/3671/
 *
 */
public class Boj3671 {
    private static boolean[] isPrime = new boolean[10_000_000];
    private static LinkedList<Integer> list = new LinkedList<>();
    private static int count;
    
    private static final char NEW_LINE = '\n';
    
    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();
        int c = Integer.parseInt(br.readLine());
        
        prime();                        // 소수 찾기
        
        while(c-- > 0) {
            char[] paper = br.readLine().toCharArray();
            count = 0;
            
            for(int i = 0; i < paper.length; i++) {
                boolean[] used = new boolean[paper.length];
                backTracking(i, paper[i] + "", paper, used);
            }
            
            while(!list.isEmpty()) {        // 탐색시 false가 된 소숫값을 다시 true로 변경
                int num = list.remove();
                isPrime[num] = true;
            }
            
            sb.append(count).append(NEW_LINE);
        }
        
        System.out.println(sb);        // 결과 출력
    }
    
    private static void prime() {
        Arrays.fill(isPrime, true);
        isPrime[0= isPrime[1= false;
        
        for(int i = 2; i < isPrime.length; i++) {            // 에라토스테네스의 체
            if(!isPrime[i]) continue;
            
            for(int j = i + i; j < isPrime.length; j += i) {
                isPrime[j] = false;
            }
        }
    }
    
    private static void backTracking(int idx, String current, char[] arr, boolean[] used) {
        if(current.length() > arr.lengthreturn;        // 길이가 주어진 문자열보다 길어진 경우
        int num = Integer.parseInt(current);
        
        used[idx] = true;
 
        if(isPrime[num]) {            // 확인된 소수를 배열에서 제거하고 리스트에 임시 저장
            list.add(num);
            
            isPrime[num] = false;
            count++;
        }
        
        for(int i = 0; i < arr.length; i++) {            
            if(used[i]) continue;                // 이미 사용된 숫자인 경우
            
            String next = current + arr[i];
            backTracking(i, next, arr, used);    // 다음으로 생성된 문자열로 재귀 탐색
            used[i] = false;                    // 백트래킹
        }
    }
}
 
cs



달라진 점은 소수를 찾았을 때 소수가 아님으로 초기화해주고, 리스트에 그 값을 담는 정도입니다.

그리고 이전 예제와 같이 주어진 숫자들로 순열을 생성하는데, 길이에 상관없이 소수면 모두 포함시킨다 정도 되겠네요.


문제 조건에 따라 앞에 0이 붙었을때 같은 수 즉, 2 또는 02, 002 .... 다 같은 수라고 했으니 정수로 변환시켜서 구분했구요.

정답율과 같이 크게 어려운 문제는 아닙니다.. ㅎㅎ


채점 결과


물론 저는 뭐.. 처음에 사용한 숫자를 초기화 안하고 뭐.. 여러가지로 실수가 좀 있었지만요.

이 문제의 경우엔 최악의 경우 자릿수 마다 모두 탐색해줘야 하고, 0 ~ 9까지 모두 들어올 수 있습니다.

따라서 시간 복잡도는 O(10^7) = 10_000_000 정도 되겠네요. (자릿수 7개마다 가능한 숫자 10개)



이해안가는 것이나, 질문 또는 잘못된 부분은 댓글로 지적 부탁드려요. 감사합니다.




문제의 저작권은 백준 온라인 저지에 있습니다.

반응형