https://www.acmicpc.net/problem/17135

 

17135번: 캐슬 디펜스

첫째 줄에 격자판 행의 수 N, 열의 수 M, 궁수의 공격 거리 제한 D가 주어진다. 둘째 줄부터 N개의 줄에는 격자판의 상태가 주어진다. 0은 빈 칸, 1은 적이 있는 칸이다.

www.acmicpc.net

코드설명

조합과 너비우선탐색(BFS)와 구현 문제입니다.

 

문제 로직입니다.

  • 1. 조합 생성
    • 재귀적 백트래킹 접근 방식을 사용하여 세 명의 궁수의 모든 가능한 조합을 생성합니다. getComb 함수는 선택된 궁수에 대해 hunter[i]를 true로 설정하고 다른 경우에는 false로 설정하여 이러한 조합을 생성합니다.
  • 2. BFS (너비 우선 탐색)
    • BFS 함수는 궁수의 방어 과정을 시뮬레이션하기 위해 사용됩니다. 이 함수는 각 선택된 궁수의 위치에서 너비 우선 탐색을 수행하고 주어진 공격 범위 D 내에서 가장 가까운 몬스터를 찾습니다. 유효한 대상을 찾으면 이를 huntList에 추가합니다. 그런 다음, 잡힌 몬스터들은 맵에서 제거되고 남은 몬스터들은 한 칸 아래로 이동합니다.
  • 3. 몬스터 제거 및 맵 업데이트를 합니다.
    • getDown 함수는 몬스터들을 한 칸 아래로 이동시킵니다. 각 열의 요소를 아래로 이동시키고 맨 위의 행을 0으로 설정합니다.

 

문제에서 실수했었던점은, 

ArrayList huntList를 한턴의 캐슬디펜스 한턴이 끝난 후 새로 선언하지 않았다는 점입니다.

그렇게 하지 않을경우, ArrayList에 사냥할 목록이 남아있어 다음턴에 몬스터들이 한 칸 내려온 이후에도 작동하여 올바르게 COunting하지 않습니다. 

while(moveCnt <= N) {
    moveCnt += 1;
    ArrayList<Node> huntList = new ArrayList<Node>();
    ...
    ...
    ...
}

 

두번쨰 실수점 또한, Queue의 초기화를 올바르게 진행하지 않았다는 점입니다.

아래와 같이 궁수를 찾고 궁수가 사냥을 시작할때면 항상 새로 시작하여 이전 궁수의 Queue 값이 남아있지 않도록 해야합니다.

for(int i=0;i<M;i++) {

    if(hunter[i] == true) {
        Queue<Node> q = new LinkedList<>();

        hunterVisited = new boolean[N+1][M];
        q.offer(new Node(N, i, 0));
        hunterVisited[N][i] = true;
        while(!q.isEmpty()) {

 

세번쨰는, 궁수가 같은 곳을 사냥할 수 있다는 점입니다.

해당사항은 alreadyEaten[][] 배열을 통해서 alreadyEaten[][]에 도달할경우 해당 칸 또한 먹을 수 있도록 해결하거나, huntList 배열을 추가하여 사냥감 목록을 넣고 실제 map은 마지막에 0 으로 갱신하는 방법이 있습니다.

단순함만을 생각한다면 huntList를 통해서 동기화 시키는 것이 낫겠지만, 속도의 경우 alreadyEaten[][]을 통해 처리하는것이 빠릅니다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	
	public static int T, N, M, D;
	public static int[][] map;
	public static int[][] copyMap;
	public static boolean[] hunter;
	public static boolean[][] hunterVisited;
	public static int answer = 0;
	public static int huntCnt = 0;
	public static int[] dx = {0,-1,1,0}; //왼쪽, 상, 하, 우
	public static int[] dy = {-1, 0, 0, 1};
	public static StringBuilder sb = new StringBuilder();
	public static void main(String[] args) throws IOException{
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    	StringTokenizer st = new StringTokenizer(br.readLine());
    	
    	N = Integer.parseInt(st.nextToken());
    	M = Integer.parseInt(st.nextToken());
    	D = Integer.parseInt(st.nextToken());
    	
    	map = new int[N+1][M];
    	hunter = new boolean[M];
    	for(int i=0;i<N;i++) {
    		st = new StringTokenizer(br.readLine());
    		for(int j=0;j<M;j++) {
    			map[i][j] = Integer.parseInt(st.nextToken());
    		}
    	}
    	getComb(0, 0);
    	
    	System.out.println(answer);
	}
	
	public static void getComb(int level, int idx) {
		if(level == 3) {
			BFS();
			answer = Math.max(answer, huntCnt);
			return ;
		}
		for(int i=idx;i<M;i++) {
			hunter[i] = true;
			getComb(level + 1, i + 1);
			hunter[i] = false;
		}
	}
	
	public static void BFS() {
		
		
		hunterVisited = new boolean[N+1][M];
		copyMap = new int[N+1][M];
		for(int i=0;i<N+1;i++) {
			for(int j=0;j<M;j++) {
				copyMap[i][j] = map[i][j];
			}
		}
		
		int moveCnt = 0;
		huntCnt = 0;
		while(moveCnt <= N) {
			moveCnt += 1;
			ArrayList<Node> huntList = new ArrayList<Node>();
			for(int i=0;i<M;i++) {
				
				if(hunter[i] == true) {
					Queue<Node> q = new LinkedList<>();
					
					hunterVisited = new boolean[N+1][M];
					q.offer(new Node(N, i, 0));
					hunterVisited[N][i] = true;
					while(!q.isEmpty()) {
						Node temp = q.poll();
						if(copyMap[temp.r][temp.c] == 1) { //잡았다면 정답에 넣는다.
							huntList.add(new Node(temp.r, temp.c, 0));
							break;
						}
						for(int dir=0;dir<4;dir++) {
							int nr = temp.r + dx[dir];
							int nc = temp.c + dy[dir];
							if(nr < 0 || nr >= N || nc < 0 || nc >= M) continue;
							
							if(hunterVisited[nr][nc] == false && temp.cnt + 1 <= D) {
								
								q.offer(new Node(nr, nc, temp.cnt + 1));
								hunterVisited[nr][nc] = true;
							}
						}
					}
					
				}
			}
			
			//사냥한것 위치 0 으로 갱신
			for(int i=0;i<huntList.size();i++) {
				Node temp = huntList.get(i);
				if(copyMap[temp.r][temp.c] == 1) {
//					System.out.println("delete "+ "tempR:"+temp.r+" tempC:"+temp.c);
					copyMap[temp.r][temp.c] = 0;	
					huntCnt += 1;
				}
			}
			getDown();	
		}
	}
	
	public static void getDown() {
		for(int i=0;i<M;i++) {
			for(int j=N-1;j>=1;j--) {
				copyMap[j][i] = copyMap[j-1][i]; 
			}
			copyMap[0][i] = 0;
		}
	}
}


class Node{
	int r;
	int c;
	int cnt;
	public Node(int r, int c, int cnt) {
		this.r=r;
		this.c=c;
		this.cnt = cnt;
	}
}

 

 

사냥꾼이 따로따로 사냥하도록 BFS를 진행했습니다.

이렇게 진행할경우 동시에 같은 사냥감을 먹어도 상관없도록 alreadyEaten[][] , 즉 이미 먹혔을 경우에도 작동하도록 조건을 추가해주어야합니다.

바로 상단의 코드에서는 사냥목록을 huntList에 넣어두고서 처리하고, 마지막에 huntList에 위치한 사냥감들을 0으로 초기화시켜줍니다.

속도적인 면에서는 아래의 코드가 빠릅니다.

package algorhythm;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	public static int N, M, D;
	public static int[][] map;
	public static int[][] storeMap;
	public static boolean[] visited;
	public static boolean[][] alreadyEaten;
	public static int answer = 0;
	public static int archor = 99;
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		D = Integer.parseInt(st.nextToken());
		
		map = new int[N+1][M];
		storeMap = new int[N+1][M];
		visited = new boolean[M];
		alreadyEaten = new boolean[N+1][M];
		for(int i=0;i<N;i++) {
			st = new StringTokenizer(br.readLine());
			for(int j=0;j<M;j++) {
				map[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		simulate(0, 0);
		System.out.println(answer);
	}
	
	
	public static void simulate(int level, int idx) {
		if(level == 3) {
			for(int i=0;i<=N;i++) {
				for(int j=0;j<M;j++) {
					storeMap[i][j] = map[i][j];
				}	
			}
            
			int huntCnt = 0;
			int turn = 0;
			while(turn++ <= N) {
				alreadyEaten = new boolean[N+1][M];
				for(int i=0;i<M;i++) {
					if(map[N][i] == archor) {
						huntCnt += protectBFS(i);
					}
				}
				
				slideDown();
			}
			
			answer = Math.max(answer, huntCnt);
			return ;
		}
		for(int i=idx;i<M;i++) {
			if(visited[i] == false) {
				visited[i] = true;
				map[N][i] = archor;
				simulate(level + 1, i + 1);
				map[N][i] = 0;	
				visited[i] = false;
			}
		}
		
	}
	
	public static int protectBFS(int c) {
		//좌, 상, 우
		int[] dx = {0, -1, 0};
		int[] dy = {-1, 0, 1};
		int huntCnt = 0;
		Queue<Node> q = new LinkedList<Node>();
		q.offer(new Node(N, c, 0));
		boolean[][] visited = new boolean[N+1][M];
		visited[N][c] = true;
		
		while(!q.isEmpty()) {
			Node temp = q.poll();
			
			if(temp.distance >= D) { // >=D 인 이유는 0번쨰 시작에서 다음것을 검사하는 순간 1 번째 거리를 검사하는 것이기 떄문입니다.
				break;
			}
			for(int dir = 0; dir < 3; dir++) {
				int nr = temp.r + dx[dir];
				int nc = temp.c + dy[dir];
				
				if(nr < 0 || nr >= N || nc < 0 || nc >= M) continue;
				if(visited[nr][nc] == true) continue;
				if(storeMap[nr][nc] > 0 || alreadyEaten[nr][nc] == true) {
					huntCnt = storeMap[nr][nc];
					//이미 먹혔다면 딱 한번만 더해줘야할 것 이다.
					//해당 부분을 이미 먹혔다는 true값으로 바꿔준다.
					//다음 헌터가 해당 부분을 마주한다면, 이미 true인 상태이므로 return 0을 통해 아무것도 먹지 못한다.
					//이미 먹힌것을 확인하는 방법은 boolean[][] 배열을 따로 하나 만들어서 전역적으로 관리한다.
					if(alreadyEaten[nr][nc] == true) {
						return 0;
					}
					alreadyEaten[nr][nc] = true;
					storeMap[nr][nc] = 0;
					return huntCnt;
				}
				visited[nr][nc] = true;
				q.offer(new Node(nr, nc, temp.distance + 1));
			}
		}
		
		return huntCnt;
	}
	
	public static void slideDown() {
		
		for(int col =0; col<M;col++) {
			for(int row = N - 1; row > 0; row--) {
				storeMap[row][col] = storeMap[row - 1][col];	
			}
			storeMap[0][col] = 0;
		}
		
	}
}

class Node{
	int r;
	int c;
	int distance = 0;
	public Node(int r, int c, int distance) {
		this.r=r;
		this.c=c;
		this.distance = distance;
	}
}

 

사냥꾼의 위치를 DFS로 완전탐색하는 코드입니다. 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	public static int N, M, D;
	public static int[][] map;
	public static int[][] storeMap;
	public static boolean[] visited;
	public static boolean[][] alreadyEaten;
	public static int answer = 0;
	public static int archor = 99;
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		D = Integer.parseInt(st.nextToken());
		
		map = new int[N+1][M];
		storeMap = new int[N+1][M];
		visited = new boolean[M];
		alreadyEaten = new boolean[N+1][M];
		for(int i=0;i<N;i++) {
			st = new StringTokenizer(br.readLine());
			for(int j=0;j<M;j++) {
				map[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		simulate(0, 0);
		System.out.println(answer);
	}
	
	
	public static void simulate(int level, int selected) {
		if(selected == 3) {
			for(int i=0;i<=N;i++) {
				for(int j=0;j<M;j++) {
					storeMap[i][j] = map[i][j];
				}	
			}
			
			int huntCnt = 0;
			int turn = 0;
			while(turn++ <= N) {
				alreadyEaten = new boolean[N+1][M];
				for(int i=0;i<M;i++) {
					if(map[N][i] == archor) {
						huntCnt += protectBFS(i);
					}
				}
				
				slideDown();
			}
			
			answer = Math.max(answer, huntCnt);
			return ;
		}
	
		if(level == M) {
			return ;
		}
		
		if(map[N][level] == 0) {
			map[N][level] = archor;
			simulate(level + 1, selected + 1);	
			map[N][level] = 0;
		}
		simulate(level + 1, selected);
	
	}
	
	public static int protectBFS(int c) {
		//좌, 상, 우
		int[] dx = {0, -1, 0};
		int[] dy = {-1, 0, 1};
		int huntCnt = 0;
		Queue<Node> q = new LinkedList<Node>();
		q.offer(new Node(N, c, 0));
		boolean[][] visited = new boolean[N+1][M];
		visited[N][c] = true;
		
		while(!q.isEmpty()) {
			Node temp = q.poll();
			
			if(temp.distance >= D) { // >=D 인 이유는 0번쨰 시작에서 다음것을 검사하는 순간 1 번째 거리를 검사하는 것이기 떄문입니다.
				break;
			}
			for(int dir = 0; dir < 3; dir++) {
				int nr = temp.r + dx[dir];
				int nc = temp.c + dy[dir];
				
				if(nr < 0 || nr >= N || nc < 0 || nc >= M) continue;
				if(visited[nr][nc] == true) continue;
				if(storeMap[nr][nc] > 0 || alreadyEaten[nr][nc] == true) {
					huntCnt = storeMap[nr][nc];
					//이미 먹혔다면 딱 한번만 더해줘야할 것 이다.
					//해당 부분을 이미 먹혔다는 true값으로 바꿔준다.
					//다음 헌터가 해당 부분을 마주한다면, 이미 true인 상태이므로 return 0을 통해 아무것도 먹지 못한다.
					//이미 먹힌것을 확인하는 방법은 boolean[][] 배열을 따로 하나 만들어서 전역적으로 관리한다.
					if(alreadyEaten[nr][nc] == true) {
						return 0;
					}
					alreadyEaten[nr][nc] = true;
					storeMap[nr][nc] = 0;
					return huntCnt;
				}
				visited[nr][nc] = true;
				q.offer(new Node(nr, nc, temp.distance + 1));
			}
		}
		
		return huntCnt;
	}
	
	public static void slideDown() {
		
		for(int col =0; col<M;col++) {
			for(int row = N - 1; row > 0; row--) {
				storeMap[row][col] = storeMap[row - 1][col];	
			}
			storeMap[0][col] = 0;
		}
		
	}
}

class Node{
	int r;
	int c;
	int distance = 0;
	public Node(int r, int c, int distance) {
		this.r=r;
		this.c=c;
		this.distance = distance;
	}
}

 

처음에 올바르게 초기화 시키지 않고, 약간의 조건 누락

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	
	public static int T, N, M, D;
	public static int[][] map;
	public static int[][] copyMap;
	public static boolean[] hunter;
	public static boolean[][] hunterVisited;
	public static int answer = 0;
	public static int huntCnt = 0;
	public static int[] dx = {0,-1,1,0}; //왼쪽, 상, 하, 우
	public static int[] dy = {-1, 0, 0, 1};
	public static StringBuilder sb = new StringBuilder();
	public static void main(String[] args) throws IOException{
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    	StringTokenizer st = new StringTokenizer(br.readLine());
    	
    	N = Integer.parseInt(st.nextToken());
    	M = Integer.parseInt(st.nextToken());
    	D = Integer.parseInt(st.nextToken());
    	
    	map = new int[N+1][M];
    	hunter = new boolean[M];
    	for(int i=0;i<N;i++) {
    		st = new StringTokenizer(br.readLine());
    		for(int j=0;j<M;j++) {
    			map[i][j] = Integer.parseInt(st.nextToken());
    		}
    	}
    	getComb(0, 0);
    	
    	System.out.println(answer);
	}
	
	public static void getComb(int level, int idx) {
		if(level == 3) {
			BFS();
			answer = Math.max(answer, huntCnt);
			return ;
		}
		for(int i=idx;i<M;i++) {
			hunter[i] = true;
			getComb(level + 1, i + 1);
			hunter[i] = false;
		}
	}
	
	public static void BFS() {
		
		Queue<Node> q = new LinkedList<>();
		ArrayList<Node> huntList = new ArrayList<Node>();
		hunterVisited = new boolean[N+1][M];
		copyMap = new int[N+1][M];
		for(int i=0;i<N+1;i++) {
			for(int j=0;j<M;j++) {
				copyMap[i][j] = map[i][j];
			}
		}
		
		int moveCnt = 0;
		huntCnt = 0;
		while(moveCnt <= N) {
			moveCnt += 1;
			for(int i=0;i<M;i++) {
				
				if(hunter[i] == true) {
					hunterVisited = new boolean[N+1][M];
					q.offer(new Node(N, i, 0));
					hunterVisited[N][i] = true;
					while(!q.isEmpty()) {
						Node temp = q.poll();
						if(copyMap[temp.r][temp.c] == 1) { //잡았다면 정답에 넣는다.
							huntList.add(new Node(temp.r, temp.c, 0));
							break;
						}
						for(int dir=0;dir<4;dir++) {
							int nr = temp.r + dx[dir];
							int nc = temp.c + dy[dir];
							if(nr < 0 || nr >= N || nc < 0 || nc >= M) continue;
							
							if(hunterVisited[nr][nc] == false && temp.cnt + 1 <= D) { 
								q.offer(new Node(nr, nc, temp.cnt + 1));
								hunterVisited[nr][nc] = true;
							}
						}
					}
					
				}
			}
			
			//사냥한것 위치 0 으로 갱신
			for(int i=0;i<huntList.size();i++) {
				Node temp = huntList.get(i);
				if(copyMap[temp.r][temp.c] == 1) {
					copyMap[temp.r][temp.c] = 0;	
					huntCnt += 1;
				}
			}
			
			getDown();	
		}
		
		
	}
	
	public static void getDown() {
		for(int i=0;i<M;i++) {
			for(int j=N-1;j>=1;j--) {
				copyMap[j][i] = copyMap[j-1][i]; 
			}
			copyMap[0][i] = 0;
		}
	}
}


class Node{
	int r;
	int c;
	int cnt;
	public Node(int r, int c, int cnt) {
		this.r=r;
		this.c=c;
		this.cnt = cnt;
	}
}

+ Recent posts