Post

BOJ_23353_승부 조작 (Java)

BOJ_23353_승부 조작 (Java)

[Gold III] 승부 조작 - 23353

문제 링크

성능 요약

메모리: 243664 KB, 시간: 996 ms

분류

다이나믹 프로그래밍

제출 일자

2024년 12월 16일 01:56:03

문제 설명

고양이 랑이와 메리는 오목 게임의 변형인 냥목 게임을 하고 있다. 냥목 게임의 규칙은 복잡하니 점수 계산 방법만 보자.

냥목 게임은 위 그림과 같은 $N \times N$ 크기의 바둑판에서 흑돌과 백돌을 이용해 진행된다.

랑이는 흑돌을, 메리는 백돌을 사용한다.

냥목 게임에서 랑이의 점수는 가로, 세로, 대각선 중 하나의 방향으로 연속하여 존재하는 가장 긴 흑돌의 길이가 된다.

잠시 집사가 돌아와서 메리가 반기러 간 사이 랑이는 메리의 돌 하나를 자신의 돌로 바꿔치기 하려고 한다. 즉, 랑이는 백돌 하나를 흑돌로 바꿀 수 있다.

랑이가 백돌 하나를 흑돌로 바꿀 때 얻을 수 있는 최대 점수를 구하는 프로그램을 작성하시오.

랑이는 흑돌을, 메리는 백돌을 사용한다.

냥목 게임에서 랑이의 점수는 가로, 세로, 대각선 중 하나의 방향으로 연속하여 존재하는 가장 긴 흑돌의 길이가 된다.

잠시 집사가 돌아와서 메리가 반기러 간 사이 랑이는 메리의 돌 하나를 자신의 돌로 바꿔치기 하려고 한다. 즉, 랑이는 백돌 하나를 흑돌로 바꿀 수 있다.

랑이가 백돌 하나를 흑돌로 바꿀 때 얻을 수 있는 최대 점수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 자연수 $N$이 주어진다. ($2 \le N \le 1,000$)

둘째 줄부터 $N$개 줄에는 줄마다 $N$개의 숫자가 공백으로 구분되어 주어진다. 이는 랑이가 돌을 바꿔치기하기 전 바둑판의 상태를 나타낸다. 각 수는 0, 1, 2 중 하나로 주어지고, 0은 비어 있는 위치를, 1은 흑돌을, 2는 백돌을 의미한다.

흑돌과 백돌은 각각 하나 이상 존재한다.

출력

랑이가 얻을 수 있는 최대 점수를 출력한다.

문제 풀이

접근 방식

  • 4차원 DP 배열 : r, c: 현재 위치 direction: 탐색 방향 (0:가로, 1:세로, 2:대각선, 3:대각선/) changed: 백돌을 바꾼 여부 (0:안바꿈, 1:바꿈)
    1
    
    static int[][][][] dp; // [r][c][direction][changed]
    
  • 탐색방향배열
    1
    2
    
    static int[] dr = {0, 1, 1, 1}; // 가로, 세로, 대각선\, 대각선/
    static int[] dc = {1, 0, 1, -1};
    

    4가지 방향을 탐색: 가로: (0,1) 세로: (1,0) 대각선: (1,1) 대각선/: (1,-1)

  • DP 계산 로직
  1. 흑돌인 경우: 다음 위치의 DP값에 1을 더합니다 범위를 벗어나면 현재 위치만 계산 (1)
1
2
3
4
5
6
7
8
9
10
11
12
13
if(board[i][j] == 1) {  // 흑돌인 경우
    for(int k = 0; k < 4; k++) {
        int nr = i + dr[k];
        int nc = j + dc[k];
        if(nr >= 1 && nr <= N && nc >= 1 && nc <= N) {
            dp[i][j][k][0] = dp[nr][nc][k][0] + 1;
            dp[i][j][k][1] = dp[nr][nc][k][1] + 1;
        } else {
            dp[i][j][k][0] = 1;
            dp[i][j][k][1] = 1;
        }
    }
}
  1. 백돌인 경우:
    • 한쪽에만 흑돌이 있는 경우: 해당 방향의 연속된 길이 + 1
    • 양쪽에 흑돌이 있는 경우: 양쪽 방향의 연속된 길이 합 + 1 (현재 위치)
  • 주요 포인트 아래에서 위로(N에서 1로) 탐색하며 DP를 계산! 백돌을 바꾸는 경우, 주변 흑돌과의 연결을 고려해야함. 경계 체크 해야함. 최댓값 갱신.

이 문제의 핵심은 백돌을 바꿨을 때 양쪽의 흑돌 연결을 어떻게 처리하느냐임. 모든 경우(한쪽만 있는 경우, 양쪽 다 있는 경우)를 고려하여 최적의 해를 찾았다.

나중에 다시 풀어보자! 까다로운 dp문제였다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
else if(board[i][j] == 2) {  // 백돌인 경우
    for(int k = 0; k < 4; k++) {
        int prevR = i - dr[k];
        int prevC = j - dc[k];
        int nextR = i + dr[k];
        int nextC = j + dc[k];
        
        // 이전 방향에 흑돌이 있는 경우
        if(prevR >= 1 && prevR <= N && prevC >= 1 && prevC <= N) {
            if(board[prevR][prevC] == 1) {
                dp[i][j][k][1] = Math.max(dp[i][j][k][1], dp[prevR][prevC][k][0] + 1);
            }
        }
        
        // 다음 방향에 흑돌이 있는 경우도 동일하게 처리
        
        // 양쪽 모두 흑돌이 있는 경우
        if(prevR >= 1 && ... && board[prevR][prevC] == 1 && board[nextR][nextC] == 1) {
            dp[i][j][k][1] = dp[prevR][prevC][k][0] + dp[nextR][nextC][k][0] + 1;
        }
    }
}

코드

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
/**
 * Author: nowalex322, Kim HyeonJae
 */
import java.io.*;
import java.util.*;

public class Main {
	static BufferedReader br;
	static BufferedWriter bw;
	static StringTokenizer st;
	static int N, res=0;
	static int[] dr = {0, 1, 1, 1}; // 가로, 세로, 대각선\, 대각선/
	static int[] dc = {1, 0, 1, -1}; 
	static int[][] board;
	static int[][][][] dp; // [r][c][direction][changed]
	public static void main(String[] args) throws Exception {
		new Main().solution();
	}

	public void solution() throws Exception {
		br = new BufferedReader(new InputStreamReader(System.in));
//		br = new BufferedReader(new InputStreamReader(new FileInputStream("input.txt")));
		bw = new BufferedWriter(new OutputStreamWriter(System.out));

		N = Integer.parseInt(br.readLine());
		
		board = new int[N+2][N+2];
		// (r, c) 위치에서 k방향으로 연속된 흑돌의 최대 길이
        dp = new int[N+2][N+2][4][2]; // direction: 0-가로, 1-세로, 2-대각선\, 3-대각선/, changed: 0-안바꿈, 1-바꿈
		List<int[]> whiteList = new ArrayList<>();

		for(int i = 1; i <= N; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 1; j <= N; j++) {
                board[i][j] = Integer.parseInt(st.nextToken());
				if(board[i][j] == 2) whiteList.add(new int[] {i, j});
            }
        }
		
		// dp 계산
        for(int i = N; i >= 1; i--) {
            for(int j = N; j >= 1; j--) {
                if(board[i][j] == 1) {
                    for(int k = 0; k < 4; k++) {
                    	int nr = i + dr[k];
                        int nc = j + dc[k];
                        if(nr >= 1 && nr <= N && nc >= 1 && nc <= N) {
                            dp[i][j][k][0] = dp[nr][nc][k][0] + 1;
                            dp[i][j][k][1] = dp[nr][nc][k][1] + 1;
                        } else {
                            dp[i][j][k][0] = 1;
                            dp[i][j][k][1] = 1;
                        }
                    }
                }
                else if(board[i][j] == 2) {
                    for(int k = 0; k < 4; k++) {
                    	int prevR = i - dr[k];
                        int prevC = j - dc[k];
                        int nextR = i + dr[k];
                        int nextC = j + dc[k];
                        if(prevR >= 1 && prevR <= N && prevC >= 1 && prevC <= N) {
                            if(board[prevR][prevC] == 1) {
                                // 이전 방향에 흑돌이 있는 경우
                                dp[i][j][k][1] = Math.max(dp[i][j][k][1], dp[prevR][prevC][k][0] + 1);
                            }
                        }
                        
                        if(nextR >= 1 && nextR <= N && nextC >= 1 && nextC <= N) {
                            if(board[nextR][nextC] == 1) {
                                // 다음 방향에 흑돌이 있는 경우
                                dp[i][j][k][1] = Math.max(dp[i][j][k][1], dp[nextR][nextC][k][0] + 1);
                            }
                        }
                        
                        // 양쪽 모두 흑돌이 있는 경우
                        if(prevR >= 1 && prevR <= N && prevC >= 1 && prevC <= N &&
                           nextR >= 1 && nextR <= N && nextC >= 1 && nextC <= N &&
                           board[prevR][prevC] == 1 && board[nextR][nextC] == 1) {
                            dp[i][j][k][1] = dp[prevR][prevC][k][0] + dp[nextR][nextC][k][0] + 1;
                        }
                    }
                }
            }
        }
        
        
        // 최대값 찾기
        int res = 0;
        for(int i = 1; i <= N; i++) {
            for(int j = 1; j <= N; j++) {
                for(int k = 0; k < 4; k++) {
                    res = Math.max(res, Math.max(dp[i][j][k][0], dp[i][j][k][1]));
                }
            }
        }
        
        System.out.println(res);
		
		bw.flush();
		bw.close();
		br.close();
	}
}
This post is licensed under CC BY 4.0 by the author.