https://algospot.com/judge/problem/read/BLOCKGAME
코드설명
동적계획법(Dynamic Programming, DP) + 게임이론(Game Theory) 을 활용합니다.
이 문제의 경우 메모리초과를 고려해야 합니다.
처음에 아래와 같이 제출하면 메모리 초과가 납니다.
이유는, byte[]는 1칸의 데이터당 8bit의 크기입니다.
byte 배열 사이즈는 1<<25 로써 2^25 (= 33,554,432 ) 개입니다.
즉,총 메모리 사용량은 2^25 * 2^8 = 2^32 = 4294967296(42억) 정도의 크기입니다.
이는 문제의 메모리인 65536 kb 크기보다 훨씬 넘어서는 크기입니다.
명확한 비교를 위해 65536 kb는 몇 사이즈일지 계산해봅시다. 65536kb = 2^16 bit * 2^10 bit 입니다. 즉, 2^26 의 크기입니다.
메모리 사이즈 초과로 런타임 에러가 발생하는 이유를 알 수 있습니다.
그대신에 boolean 으로 2^1 크기. 즉, 1bit 크기의 boolean을 사용함으로써 2^26 개를 딱 맞추어서 사용할 수 있습니다.
또, 이 경우 boolean으로만 표현해도 괜찮을까?라는 생각이 듭니다.
이 문제의 경우 비기는 경우는 없고, 반드시 이기는 경우와 지는 경우 2가지만 있기에 이기는 경우만 캐싱해서 처리합니다.(다만, 지는 경우를 캐싱하지 않아서 시간이 조금 더 추가되겠습니다.)
코드
첫번쨰 오류 코드. (byte를 사용하면서 메모리 초과 발생)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
public class Main {
private static int C, N, W, H, K;
private static int answer = 0;
private static char[][] arr;
//가능한 모든 블록 위치를 저장하는 리스트입니다.
private static List<Integer> moves = new ArrayList<>();
//게임상태에 대한 캐시(메모이제이션)
private static byte[] cache = new byte[1 << 25];
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
//가능한 모든 블록 위치 미리 계산
precalc();
//게임상태에 대한 캐시(메모이제이션)
cache = new byte[1 << 25];
Arrays.fill(cache, (byte) -1);
arr = new char[5][5];
C = Integer.parseInt(st.nextToken());
while(C-- > 0){
int board = 0;
for(int i=0;i<5;i++) {
st = new StringTokenizer(br.readLine());
String str = st.nextToken();
for(int j=0;j<5;j++) {
arr[i][j] = str.charAt(j);
if(arr[i][j] == '#') {
board += cell(i, j);
}
}
}
byte result = play(board);
if(result == 1) {
System.out.println("WINNING");
}else {
System.out.println("LOSING");
}
System.out.println(moves.size());
System.out.println(cache.length);
}
}
private static int cell(int r, int c) {
return 1 << (r * 5 + c);
}
//가능한 모든 블록 위치를 미리 계산
private static void precalc() {
//'ㄴ'자 모양 블록 계산
for(int r=0; r<4; r++) {
for(int c =0; c<4; c++) {
List<Integer> cells = new ArrayList<>();
for(int dr = 0; dr < 2; dr++) {
for(int dc = 0; dc < 2; dc++) {
cells.add(cell(r + dr, c + dc));
}
}
int square = cells.get(0) + cells.get(1) + cells.get(2) + cells.get(3);
for(int i=0; i<4; i++) {
moves.add(square - cells.get(i));
}
}
}
//두 칸짜리 블록 계산
for(int i=0; i<5; i++) {
for(int j=0; j<4; j++) {
//가로방향 두 칸 블록
moves.add(cell(i, j) + cell(i, j + 1));
//세로방향 두 칸 블록
moves.add(cell(j, i) + cell(j + 1, i));
}
}
}
static byte play(int board) {
if(cache[board] != (byte) -1) {
return cache[board];
}
byte ret = 0;
//가능한 모든 움직임에 대해 검사
for(int i=0;i<moves.size();i++) {
//블록을 놓을 수 있는 경우
if( (moves.get(i) & board) == 0) {
//다음 상태에서 상대방이 지면 현재 플레이어가 이깁니다.
if( play(moves.get(i) | board) == 0 ) {
ret = 1;
break;
}
}
}
return cache[board] = ret;
}
}
정답코드입니다. boolean 을 사용하여 메모리 캐싱합니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
public class Main {
private static int C, N, W, H, K;
private static int answer = 0;
private static char[][] arr;
//가능한 모든 블록 위치를 저장하는 리스트입니다.
private static List<Integer> moves = new ArrayList<>();
//게임상태에 대한 캐시(메모이제이션)
private static boolean[] cache;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
//가능한 모든 블록 위치 미리 계산
precalc();
//게임상태에 대한 캐시(메모이제이션)
cache = new boolean[1 << 25];
// Arrays.fill(cache, (byte) -1);
arr = new char[5][5];
C = Integer.parseInt(st.nextToken());
while(C-- > 0){
int board = 0;
for(int i=0;i<5;i++) {
st = new StringTokenizer(br.readLine());
String str = st.nextToken();
for(int j=0;j<5;j++) {
arr[i][j] = str.charAt(j);
if(arr[i][j] == '#') {
board += cell(i, j);
}
}
}
boolean result = play(board);
if(result == true) {
System.out.println("WINNING");
}else {
System.out.println("LOSING");
}
}
}
private static int cell(int r, int c) {
return 1 << (r * 5 + c);
}
//가능한 모든 블록 위치를 미리 계산
private static void precalc() {
//'ㄴ'자 모양 블록 계산
for(int r=0; r<4; r++) {
for(int c =0; c<4; c++) {
List<Integer> cells = new ArrayList<>();
for(int dr = 0; dr < 2; dr++) {
for(int dc = 0; dc < 2; dc++) {
cells.add(cell(r + dr, c + dc));
}
}
int square = cells.get(0) + cells.get(1) + cells.get(2) + cells.get(3);
for(int i=0; i<4; i++) {
moves.add(square - cells.get(i));
}
}
}
//두 칸짜리 블록 계산
for(int i=0; i<5; i++) {
for(int j=0; j<4; j++) {
//가로방향 두 칸 블록
moves.add(cell(i, j) + cell(i, j + 1));
//세로방향 두 칸 블록
moves.add(cell(j, i) + cell(j + 1, i));
}
}
}
static boolean play(int board) {
if(cache[board] != false) {
return cache[board];
}
boolean ret = false;
//가능한 모든 움직임에 대해 검사
for(int i=0;i<moves.size();i++) {
//블록을 놓을 수 있는 경우
if( (moves.get(i) & board) == 0) {
//다음 상태에서 상대방이 지면 현재 플레이어가 이깁니다.
if( play(moves.get(i) | board) == false ) {
ret = true;
break;
}
}
}
return cache[board] = ret;
}
}