알고리즘/백트래킹

[백준 1799] - [백트래킹] - 비숍 (JAVA)

팡스 2018. 12. 26. 17:13

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


이 문제는 이전에 풀었던 N-Queen 문제의 확장판 문제이다.


2018/11/21 - [알고리즘/백트래킹] - [백준-9663]-[백트래킹] - N-Queen



그리고 백트래킹 문제의 최고존엄 문제 급이지 않을까 생각한다.


이 문제를 잘못 접근하게 되면 문제는 풀겠지만 시간 초과에 걸리게 된다. 


잘못접근하는 대부분의 이슈는 하나의 체스판을 전체 탐색을 할경우 생기게 된다. 


비숍은 대각선으로 움직인다. 그렇다면 대각선에는 비숍이 위치할 수 없다.


아래처럼 2,2 에 비숍이 위치할 경우 대각선에 위치한 모든 좌표에는 비숍을 놓을 수 없다.


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



또 여기서 하나 중요하게 고려해야할게 있는데 2, 2에 비숍을 놓았을경우 비숍의 좌표 기준 상, 하, 좌, 우 는 비숍을 놓을 수 있는지 탐색할 필요가 없다.


즉 시간초과가 나는 가장 큰 이유는 (y, x) 좌표에 비숍을 놓을 경우 상,하,좌,우 까지 같이 탐색을 하기 때문이다.


체스판은 흑색 칸과 백색 칸이 있고 이 칸들은 한칸 건너서 위치한다.


흑색과 백색 칸은 서로에게 영향을 주지 않는다.


 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


즉 탐색 자체를 흑색칸에 비숍을 놓았을 경우 흑색 칸에 대한 탐색만 하고 백색칸에 비숍을 놓았을 경우 백색칸에 대한 탐색을 진행한 다음 두번의 탐색에 걸쳐 나온 비숍을 놓을 수 있는 자리수를 더해서 출력하면 되는 문제이다. 



흑색칸은 (1, 1) 부터 탐색을 진행했고


백색칸은 (1,2) 부터 탐색을 진행했다.


x의 좌표를 2씩 증가 시키면서 같은 색의 칸을 탐색하게 했고 x 의 좌표가 체스의 크기보다 켜질 경우 

y 좌표를 1씩 증가 시키고 x 좌표는 y 좌표에 따라서 초기화 해줬다. 


해당 좌표에 비숍을 놓을 수 있는지에 대한 탐색 자체도 상하좌우에 대각선까지 탐색하지 않고 비숍 기준 대각선으로만 탐색을 진해하여 시간 초과를 방지했다.


소스

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
121
122
123
124
125
126
127
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
 
public class Main {
    public static int size;
    public static int[][] chess;
    
    public static int[] dy = {-1-111}; // 좌상, 우상, 좌하, 우하
    public static int[] dx = {-11-11}; // 좌상, 우상, 좌하, 우하
    
    public static int b_cnt = 0;
    public static int w_cnt = 0;
    
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;
        
        size = Integer.parseInt(br.readLine());
        
        chess = new int[size+1][size+1];
        
        // 입력
        for(int i=1; i <= size; i++) {
            st = new StringTokenizer(br.readLine());
            
            for(int j=1; j <= size; j++) {
                chess[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        
        // 흑색 자리에 둘 수 있는 비숍의 수를 탐색한다.
        boolean[][] black_visited = new boolean[size+1][size+1];
        black_search(black_visited, 110);
        
        // 백색 자리에 둘 수 있는 비숍의 수를 탐색한다.
        boolean[][] white_visited = new boolean[size+1][size+1];
        white_search(white_visited, 120);
        
        // 흑색, 백색 자리에 둘 수 있는 비숍의 수를 합하여 출력한다.
        System.out.println(b_cnt+w_cnt);
    }
 
    public static void black_search(boolean[][] visited, int y, int x, int cnt) {
        // 검은자리에 둘 수 있는 횟수가 기존 횟수보다 크면 값을 변경한다.
        b_cnt = Math.max(cnt, b_cnt);
        
        // x 표가 체스판의 크기보다 크면 좌표를 다음칸의 첫번째 x 좌표로 이동 시켜준다.
        // 단, 흑색 자리는 y가 홀수일 경우 x의 시작 점이 1부터 시작되고
        //    짝수일 경우 2부터 시작된다.
        if(x > size) {
            y += 1;
            x = (y%2 == 0)?2:1;
        }
        
        // y의 좌표가 체스판의 사이즈를 초과할경우 탐색을 종료한다.
        if(y > size) return;
        
        
        // 현재자리에 비숍을 놓을 수 있는지 확인
        // 현재자리에 비숍을 놓을 수 있다면 비숍을 놓고 다음 탐색을 진행한다.
        // 다음 탐색을 진행할 때에는 x좌표를 2증가 시켜줘야 한다. 이유는 옆 자리는 색이 다른 자리니깐
        // 비숍을 둘 경우에는 cnt를 1 증가 시켜줘야한다.
        if(isAble(visited, y, x)) {
            visited[y][x] = true;
            black_search(visited, y, x+2, cnt+1);
            visited[y][x] = false;
        }
        
        // 현재자리에 비숍을 놓을수 있더라도 안놓을수도 있기 때문에 비숍을 놓지 않았을 경우에 대한 탐색도 진행한다.
        // 다음 탐색을 진행할 때에는 x좌표를 2증가 시켜줘야 한다. 이유는 옆 자리는 색이 다른 자리니깐
        // 비숍을 놓지 않았을 경우에는 cnt를 증가시킬 필요가 없다.
        black_search(visited, y, x+2, cnt);
    }
    
    public static void white_search(boolean[][] visited, int y, int x, int cnt) {
        // 백색 자리에 둘 수 있는 횟수가 기존 횟수보다 크면 값을 변경한다.
        w_cnt = Math.max(cnt, w_cnt);
        
        // x 표가 체스판의 크기보다 크면 좌표를 다음칸의 첫번째 x 좌표로 이동 시켜준다.
        // 단, 백색 자리는 y가 홀수일 경우 x의 시작 점이 2부터 시작되고
        //    짝수일 경우 1부터 시작된다.
        if(x > size) {
            y += 1;
            x = (y%2 == 0)?1:2;
        }
        
        // y의 좌표가 체스판의 사이즈를 초과할경우 탐색을 종료한다.
        if(y > size) return;
        
        // 현재자리에 비숍을 놓을 수 있는지 확인
        // 현재자리에 비숍을 놓을 수 있다면 비숍을 놓고 다음 탐색을 진행한다.
        // 다음 탐색을 진행할 때에는 x좌표를 2증가 시켜줘야 한다. 이유는 옆 자리는 색이 다른 자리니깐
        // 비숍을 둘 경우에는 cnt를 1 증가 시켜줘야한다.
        if(isAble(visited, y, x)) {
            visited[y][x] = true;
            white_search(visited, y, x+2, cnt+1);
            visited[y][x] = false;
        }
        
        // 현재자리에 비숍을 놓을수 있더라도 안놓을수도 있기 때문에 비숍을 놓지 않았을 경우에 대한 탐색도 진행한다.
        // 다음 탐색을 진행할 때에는 x좌표를 2증가 시켜줘야 한다. 이유는 옆 자리는 색이 다른 자리니깐
        // 비숍을 놓지 않았을 경우에는 cnt를 증가시킬 필요가 없다.
        white_search(visited, y, x+2, cnt);
    }
    
    
    public static boolean isAble(boolean[][] visited, int y, int x) {
        if(chess[y][x] == 0return false// 0이면 놓을 수 없다.
        
        for(int i=0; i < 4; i ++) {
            int yy = dy[i] + y;
            int xx = dx[i] + x;
            
            for(int j=1; j <= size; j++) {
                if(yy > 0 && xx > 0 && yy <= size && xx <= size) {
                    if(visited[yy][xx]) return false;
                    
                    yy += dy[i];
                    xx += dx[i];
                }
            }
        }
        return true;
    }
}
cs