[코드트리 - L15] 23년 상반기 오전 1번 포탑 부수기

2026. 4. 10. 19:23·알고리즘

 

 

 

코딩테스트 기출 문제 설명: 포탑 부수기 | 코드트리

코딩테스트 기출 문제 포탑 부수기의 상세 설명입니다. 문제 요구사항을 정확히 파악하고 효율적인 알고리즘을 설계해보세요.

www.codetree.ai

 

  • 소요 시간 : 4시간!!

 

문제 풀이

1. 공격자 선정

2. 타겟 설정해서 레이저 공격 및 포탄 공격

3-1. 레이저 공격

3-2. 포탄 공격

4. 포탑 부서짐 및 정비

 

크게 위와 같이 나눌 수 있다.

 

1. 공격자 선정

  • 0 이 아닌 값들 중 가장 약한 포탑을 공격자로 선정
  • 2개 이상이면, 가장 최근 공격 포탑
  • 최근 공격 포탑도 2개 이상이면, (행 + 열) 값이 가장 큰 포탑
  • (행 + 열) 값도 같은게 2개 이상이면, 열이 가장 큰 포탑
  • 선정 후 공격력 (N  + M)만큼 추가한다.

가장 최근 공격 포탑은 lastAttackTime 변수와 배열을 통해 공격했다면 +1을 해주어 가장 큰 값이 가장 최근 공격 포탑으로 설정했다. 

static Top pickAttack() {

	// 공격자 선정: 가장 약한 포탑
	PriorityQueue<Top> pq = new PriorityQueue<>((a, b) -> {
		if (a.power != b.power) {
			return a.power - b.power; // 오름차순
		} else if (a.lastAttackTime != b.lastAttackTime) {
			return b.lastAttackTime - a.lastAttackTime; // 내림차순
		} else if ((a.x + a.y) != (b.x + b.y)) {
			return (b.x + b.y) - (a.x + a.y); // 가장 큰 값
		}
		return b.y - a.y; // 가장 큰 값
	});

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			if (map[i][j] > 0) {
				Top t = new Top(i, j, map[i][j]);
				t.lastAttackTime = lastAttackTime[i][j];
				pq.add(t);
			}
		}
	}
	return pq.peek();
}

 

 

2. 공격할 타겟 설정

  • 공격자와 반대로 우선순위를 지정한다.
	static Top pickTarget() {

		// 공격자 선정: 가장 강한 포탑
		PriorityQueue<Top> pq = new PriorityQueue<>((a, b) -> {
			if (a.power != b.power) {
				return b.power - a.power; // 가장 강한 순
			} else if (a.lastAttackTime != b.lastAttackTime) {
				return a.lastAttackTime - b.lastAttackTime; // 작은 순
			} else if ((a.x + a.y) != (b.x + b.y)) {
				return (a.x + a.y) - (b.x + b.y); // 가장 작은 값
			}
			return a.y - b.y; // 가장 작은 값
		});

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (map[i][j] > 0) {
					Top t = new Top(i, j, map[i][j]);
					t.lastAttackTime = lastAttackTime[i][j];
					pq.add(t);
				}
			}
		}

		return pq.peek();
	}

 

 

3. 공격

  • 레이저 공격을 먼저 시행하고, 불가능할 경우 포탄 공격을 시행할 것
  • 레이저 공격
    • 막혔다면 반대편으로 이동 가능하다 ex) N = 4일 때, (0, 3) -> (0, 0)
    • 공격자에서 타겟으로 가는 최단 경로로 공격하고 없으면 포탄 공격을 시행한다.
    • 최단 경로가 여러 개면 우하좌상 순으로 선택
    • 타겟은 공격력만큼 줄어들고, 경로에 존재하는 포탑들은 (공격력 / 2)만큼 줄어든다.
  • 포탄 공격
    • 바로 타겟으로 폭탄이 떨어져, 타겟은 공격력만큼 줄고, 주변 8방향 또한 (공격력 / 2) 만큼 줄어든다.

 

1) 레이저 공격

  • 지나온 경로를 Map에 넣어 (key, value) = (현재 경로, 이전 경로) 로 넣어 최단거리로 가능 경로만 찾아냈다.
  • trace 를 통해 되돌아 가는 길이 있다면 (공격력 / 2)만큼 제거한다. 
  • 그리고 추후 포탑 정비를 위해 isAttack 배열의 지나온 위치를 모두 true로 설정한다.
  • 주의 할 점: target과 attack의 위치는 피해야함
	private static boolean laser(Top attack, Top target) {

		Queue<Top> queue = new ArrayDeque<>();
		queue.offer(attack);

		boolean[][] visited = new boolean[N][M];
		visited[attack.x][attack.y] = true;

		Map<String, String> route = new HashMap<>();
		route.put(attack.x + "," + attack.y, null);

		while (!queue.isEmpty()) {

			Top cur = queue.poll();
			String curKey = cur.x + "," + cur.y;

			for (int d = 0; d < 4; d++) {
				int nx = cur.x + dx[d];
				int ny = cur.y + dy[d];

				if (!isRange(nx, ny)) {
					nx = (nx + N) % N;
					ny = (ny + M) % M;
				}

				if (nx == target.x && ny == target.y) {

					String targetKey = nx + "," + ny;
					route.put(targetKey, curKey);

					// 목적지 공격
					map[nx][ny] -= attack.power;
					isAttack[nx][ny] = true;

					// 경로 공격
					String trace = targetKey;
					while (true) {
						trace = route.get(trace);
						if (trace == null)
							break;

						String[] pos = trace.split(",");
						int tx = Integer.parseInt(pos[0]);
						int ty = Integer.parseInt(pos[1]);

						if (tx == attack.x && ty == attack.y)
							continue;
						map[tx][ty] -= attack.power / 2;
						isAttack[tx][ty] = true;
					}

					return true;
				}

				// 방문 안하고 부서지지 않앗을 경우
				if (!visited[nx][ny] && map[nx][ny] > 0) {

					visited[nx][ny] = true;
					String next = nx + "," + ny;
					route.put(next, curKey);
					queue.offer(new Top(nx, ny, map[nx][ny]));

				}
			}
		}

		return false;

	}

 

 

2) 포탄 공격

  • 바로 목적지로 공격하는 상황
  • 8방향 체크 후, (공격력 / 2) 만큼 감소시킨 후 isAttack에 true로 변경해준다.
  • 여기서 공격자의 위치랑 동일하다면 피해야 한다!!! => 이거 때문에 계속 틀렸다 정말..
	static void bomb(Top attack, Top target) {

		map[target.x][target.y] -= attack.power;
		isAttack[target.x][target.y] = true;

		// 주변 8방향 체크
		for (int d = 0; d < 8; d++) {
			int nx = target.x + dx2[d];
			int ny = target.y + dy2[d];

			if (!isRange(nx, ny)) {
				nx = (nx + N) % N;
				ny = (ny + M) % M;
			}
			
			if (nx == attack.x && ny == attack.y) continue;

			map[nx][ny] -= (int) attack.power / 2;
			isAttack[nx][ny] = true;
		}

	}

 

 

4. 포탑 부서짐 및 정비

  • 부서짐은 음수인 값들을 모두 0으로 보정한다.
  • 정비는 공격에 영향을 받지 않은 포탑을 모두 공격력 + 1을 진행해준다. isAttack에 false이고 > 0 이면 모두 +1을 진행해주었다.
	static void clean() {
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (!isAttack[i][j] && map[i][j] > 0)
					map[i][j]++;
			}
		}
	}

	static void broken() {

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (map[i][j] < 0)
					map[i][j] = 0;
			}
		}
	}

 

 

5. 그외.. 등등

그리고 이건 문제 제일 처음에 있던 조건인데, 제거했더니 런타임 에러가 발생했다. 문제를 꼼꼼히 읽는 것이 중요!

 

	static boolean isStop() {

		// 포탑이 1개가 된다면 즉시 중지
		int cnt = 0;
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (map[i][j] > 0)
					cnt++;
			}
		}

		return cnt > 1 ? false : true;
	}

 

 

다른 오류로는 target과 attack의 좌표가 계속 표시되어서, 공격자인데도 공격력만큼 감소되거나 그런 경우가 많아서 전부 예외처리 해주었다. 그리고 Top 클래스에서는 power를 (N  + M)만큼 추가해주고는, map[i][j] 에는 공격력을 추가해주지 않아 갱신이 안되는 문제도 있었다. 마찬가지로 lastAttackTime도 다음 공격 턴에서 값이 유지가 안되어서 배열을 따로 지정해 초기화 해주었다.

 

그 결과 4시간이나 걸려버림..ㅠㅠ

 

 

 


전체 코드

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

	static class Top {
		int x, y;
		int power;
		int lastAttackTime;

		Top(int x, int y, int power) {
			this.x = x;
			this.y = y;
			this.power = power;
		}

	}

	static int N, M, K;
	static int[][] map;
	static int[] dx = { 0, 1, 0, -1 }; // 우하좌상
	static int[] dy = { 1, 0, -1, 0 };
	static int[] dx2 = { 0, 1, 1, 1, 0, -1, -1, -1 };
	static int[] dy2 = { 1, 1, 0, -1, -1, -1, 0, 1 };
	static boolean[][] isAttack;
	static int[][] lastAttackTime;

	public static void main(String[] args) throws Exception {

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] split = br.readLine().split(" ");
		N = Integer.parseInt(split[0]);
		M = Integer.parseInt(split[1]);
		K = Integer.parseInt(split[2]);

		map = new int[N][M];
		lastAttackTime = new int[N][M];

		for (int i = 0; i < N; i++) {
			split = br.readLine().split(" ");
			for (int j = 0; j < M; j++) {
				map[i][j] = Integer.parseInt(split[j]);
			}
		}

		int cnt = 0;
		while (K > 0) {

			K--;
			// 포탑이 1개가 된다면 즉시 중지
			if (isStop()) {
				break;
			}

			// 1. 공격자 선정
			isAttack = new boolean[N][M];
			Top attack = pickAttack();

			// 2. 공격 받을 포탑 선택
			Top target = pickTarget();
			
			// 공격력 설정
			// 공격력 상승
			map[attack.x][attack.y] += (N + M);
			attack.power = map[attack.x][attack.y];

			attack.lastAttackTime = ++cnt; // 최근 공격
			lastAttackTime[attack.x][attack.y] = cnt;

			isAttack[attack.x][attack.y] = true; // 이번 공격과 관련되어 있음

			// 3. 공격 - 레이저 or 포탄 공격
			if (!laser(attack, target)) {
				bomb(attack, target);
			}

			// 4. 포탑 부서짐
			broken();

			// 5. 포탑 정비
			clean();

		}

		int ans = -1;
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (map[i][j] > 0)
					ans = Math.max(ans, map[i][j]);
			}
		}

		System.out.println(ans);
	}

	private static void clean() {
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (!isAttack[i][j] && map[i][j] > 0)
					map[i][j]++;
			}
		}
	}

	private static void broken() {

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (map[i][j] < 0)
					map[i][j] = 0;
			}
		}
	}

	private static void bomb(Top attack, Top target) {

		map[target.x][target.y] -= attack.power;
		isAttack[target.x][target.y] = true;

		// 주변 8방향 체크
		for (int d = 0; d < 8; d++) {
			int nx = target.x + dx2[d];
			int ny = target.y + dy2[d];

			if (!isRange(nx, ny)) {
				nx = (nx + N) % N;
				ny = (ny + M) % M;
			}

            if (nx == attack.x && ny == attack.y) continue;

			map[nx][ny] -= (int) attack.power / 2;
			isAttack[nx][ny] = true;
		}

	}

	private static boolean laser(Top attack, Top target) {

		Queue<Top> queue = new ArrayDeque<>();
		queue.offer(attack);

		boolean[][] visited = new boolean[N][M];
		visited[attack.x][attack.y] = true;

		Map<String, String> route = new HashMap<>();
		route.put(attack.x + "," + attack.y, null);

		while (!queue.isEmpty()) {

			Top cur = queue.poll();
			String curKey = cur.x + "," + cur.y;

			for (int d = 0; d < 4; d++) {
				int nx = cur.x + dx[d];
				int ny = cur.y + dy[d];

				if (!isRange(nx, ny)) {
					nx = (nx + N) % N;
					ny = (ny + M) % M;
				}

				if (nx == target.x && ny == target.y) {

					String targetKey = nx + "," + ny;
					route.put(targetKey, curKey);

					// 목적지 공격
					map[nx][ny] -= attack.power;
					isAttack[nx][ny] = true;

					// 경로 공격
					String trace = targetKey;
					while (true) {
						trace = route.get(trace);
						if (trace == null)
							break;

						String[] pos = trace.split(",");
						int tx = Integer.parseInt(pos[0]);
						int ty = Integer.parseInt(pos[1]);

						if (tx == attack.x && ty == attack.y)
							continue;
						map[tx][ty] -= attack.power / 2;
						isAttack[tx][ty] = true;
					}

					return true;
				}

				// 방문 안하고 부서지지 않앗을 경우
				if (!visited[nx][ny] && map[nx][ny] > 0) {

					visited[nx][ny] = true;
					String next = nx + "," + ny;
					route.put(next, curKey);
					queue.offer(new Top(nx, ny, map[nx][ny]));

				}
			}
		}

		return false;

	}

	private static Top pickTarget() {

		// 공격자 선정: 가장 강한 포탑
		PriorityQueue<Top> pq = new PriorityQueue<>((a, b) -> {
			if (a.power != b.power) {
				return b.power - a.power; // 가장 강한 순
			} else if (a.lastAttackTime != b.lastAttackTime) {
				return a.lastAttackTime - b.lastAttackTime; // 작은 순
			} else if ((a.x + a.y) != (b.x + b.y)) {
				return (a.x + a.y) - (b.x + b.y); // 가장 작은 값
			}
			return a.y - b.y; // 가장 작은 값
		});

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (map[i][j] > 0) {
					Top t = new Top(i, j, map[i][j]);
					t.lastAttackTime = lastAttackTime[i][j];
					pq.add(t);
				}
			}
		}

		// System.out.println(pq.peek().x + " " + pq.peek().y + " " + pq.peek().power);
		return pq.peek();

	}

	private static Top pickAttack() {

		// 공격자 선정: 가장 약한 포탑
		PriorityQueue<Top> pq = new PriorityQueue<>((a, b) -> {
			if (a.power != b.power) {
				return a.power - b.power; // 오름차순
			} else if (a.lastAttackTime != b.lastAttackTime) {
				return b.lastAttackTime - a.lastAttackTime; // 내림차순
			} else if ((a.x + a.y) != (b.x + b.y)) {
				return (b.x + b.y) - (a.x + a.y); // 가장 큰 값
			}
			return b.y - a.y; // 가장 큰 값
		});

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (map[i][j] > 0) {
					Top t = new Top(i, j, map[i][j]);
					t.lastAttackTime = lastAttackTime[i][j];
					pq.add(t);
				}
			}
		}

		// System.out.println(pq.peek().x + " " + pq.peek().y + " " + pq.peek().power);
		return pq.peek();

	}

	static boolean isStop() {

		// 포탑이 1개가 된다면 즉시 중지
		int cnt = 0;
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				if (map[i][j] > 0)
					cnt++;
			}
		}

		return cnt > 1 ? false : true;
	}

	static boolean isRange(int x, int y) {
		if (x < 0 || y < 0 || x >= N || y >= M)
			return false;
		return true;
	}

}
저작자표시 비영리 변경금지 (새창열림)

'알고리즘' 카테고리의 다른 글

[코드트리 - L13] 25년 상반기 오후 1번 미생물 연구  (1) 2026.04.09
[코드트리 - L12] 25년 상반기 오전 1번 민트초코우유  (1) 2026.04.08
[코드트리 - L13] 25년 하반기 오후 1번 AI 로봇청소기  (0) 2026.04.07
[코드트리 - L12] 25년 하반기 오전 1번 문제 - 택배 하차  (0) 2026.04.07
[알고리즘] 시간 복잡도 & 공간 복잡도  (0) 2026.04.05
'알고리즘' 카테고리의 다른 글
  • [코드트리 - L13] 25년 상반기 오후 1번 미생물 연구
  • [코드트리 - L12] 25년 상반기 오전 1번 민트초코우유
  • [코드트리 - L13] 25년 하반기 오후 1번 AI 로봇청소기
  • [코드트리 - L12] 25년 하반기 오전 1번 문제 - 택배 하차
수웅
수웅
  • 수웅
    야금야금 공부
    수웅
  • 전체
    오늘
    어제
    • 분류 전체보기 (106) N
      • 코딩 (5)
      • 알고리즘 (59)
      • CS (19) N
      • 취준 (1)
      • 안드로이드 (17)
        • 코틀린 (6)
        • 정리 (10)
        • 프로젝트 (0)
      • Error (1)
      • Git (2)
      • 기타 (2)
  • 블로그 메뉴

    • 홈
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
수웅
[코드트리 - L15] 23년 상반기 오전 1번 포탑 부수기
상단으로

티스토리툴바