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; } }
'알고리즘 > 백준' 카테고리의 다른 글
[백준] 1726 로봇 - 너비우선탐색(BFS) JAVA (0) | 2023.11.10 |
---|---|
[백준] 17142 연구소 3 - 조합(Combination) + 너비우선탐색(BFS) + DFS(깊이우선탐색) JAVA (0) | 2023.11.09 |
[백준] 1941 소문난 칠공주 - 조합(Combination) + BFS(너비우선탐색) JAVA (0) | 2023.11.09 |
[백준] 1516 게임 개발 - DP + 위상 정렬(Topology Sort) JAVA (0) | 2023.11.09 |
[백준] 1937 욕심쟁이 판다 - DP + 깊이우선탐색(DFS) JAVA (0) | 2023.11.08 |