PS

[Codetree] 포탑 부수기

oxdjww 2025. 4. 9. 17:48
728x90
반응형

2023 상반기 오전 1번 문제

순수 시간 5시간 정도 갈아 넣은 끝에 구현한 포탑 부수기..

처음에 문제 이해하고 설계를 했으나 객체지향 특징은 잘 못 살리고 절차지향으로 설계해서 디버깅이 오래 걸렸다

 

1차 구현은 2시간 안으로 됐는데, 제공하는 테케 2개를 다 맞히고 나머지 히든을 맞춰보다가 계속 틀렸다.

 

2% 4% 6% 8%에서 계속 끊겼었음..

 

제출 코드

import java.io.*;
import java.util.*;

public class Main {
    private static Tower[][] board;
    private static int n;
    private static int m;
    private static int k;
    private static boolean[][] previousAttack;
    private static boolean[][] visited;

    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());
        k = Integer.parseInt(st.nextToken());

        board = new Tower[n][m];
        previousAttack = new boolean[n][m];
        visited = new boolean[n][m];
        for(int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for(int j = 0; j < m; j++) {
                board[i][j] = new Tower(
                    i, j,
                    Integer.parseInt(st.nextToken())
                );
            }
        }        

        for(int i = 1; i <= k; i++) {
            int[] attacker = selectAttacker();
            if (attacker == null) break;
            // 공격자도 공격 관련에 포함
            previousAttack[attacker[0]][attacker[1]] = true;
            // 최근 공격 저장
            board[attacker[0]][attacker[1]].attackHistory = i;
            // 버프
            board[attacker[0]][attacker[1]].power += (n+m);
            // System.out.println(attacker[0] + ", " + attacker[1]);
            int[] victim = selectVictim(attacker);
            if (victim == null) break;

            // System.out.println(victim[0] + ", " + victim[1]);
            attack(attacker, victim);
            repair();
            if (countAliveTowers() <= 1) break;
        }
        int max = Integer.MIN_VALUE;
        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[i].length; j++) {
                max = Math.max(max, board[i][j].power);
            }
        }
        System.out.println(max);
    }

    private static int countAliveTowers() {
        int cnt = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (board[i][j].power > 0) cnt++;
            }
        }
        return cnt;
    }


    private static int[] selectAttacker() {
        int minPower = Integer.MAX_VALUE;
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(board[i][j].power != 0) {
                    minPower = Math.min(minPower, board[i][j].power);
                }
            }
        }

        if(minPower == Integer.MAX_VALUE) return null;

        List<Tower> smallerPower = new ArrayList<>();

        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(board[i][j].power != 0) {
                    if(board[i][j].power == minPower) {
                        smallerPower.add(board[i][j]);
                    }
                }                                       
            }
        }

        if(smallerPower.size() == 1) {
            // 1. 공격력 가장 낮은 포탑 (1개)
            return new int[]{smallerPower.get(0).x, smallerPower.get(0).y};
        } else {
            // 2. 가장 최근 공격
            Collections.sort(smallerPower, new Comparator<Tower>() {
                @Override
                public int compare(Tower t1, Tower t2) {
                    return Integer.compare(t2.attackHistory, t1.attackHistory);
                }
            });
            int recentMin = smallerPower.get(0).attackHistory;
            List<Tower> recent = new ArrayList<>();
            for(int i = 0; i < smallerPower.size(); i++) {
                if(smallerPower.get(i).attackHistory == recentMin) {
                    recent.add(smallerPower.get(i));
                }
            }

            if(recent.size() == 1) {
                return new int[]{recent.get(0).x, recent.get(0).y};
            } else {
                // 3. 행 열 합 가장 큰
                Collections.sort(recent, new Comparator<Tower>() {
                    @Override
                    public int compare(Tower t1, Tower t2) {
                        return Integer.compare(t2.x + t2.y, t1.x + t1.y);
                    }
                });
                int rowColSumMax = recent.get(0).x + recent.get(0).y;
                List<Tower> rowColSum = new ArrayList<>();
                for(int i = 0; i < recent.size(); i++) {
                    if(recent.get(i).x + recent.get(i).y == rowColSumMax) {
                        rowColSum.add(recent.get(i));
                    }
                }
                                    
                if(rowColSum.size() == 1) {
                    return new int[]{rowColSum.get(0).x, rowColSum.get(0).y};
                } else {
                    // 4. 열 가장 큰
                    Collections.sort(rowColSum, new Comparator<Tower>() {
                        @Override
                        public int compare(Tower t1, Tower t2) {
                            return Integer.compare(t2.y, t1.y);
                        }
                    });
                    return new int[]{rowColSum.get(0).x, rowColSum.get(0).y};
                }
            }
        }
    } // selectAttacker()

    private static int[] selectVictim(int[] attacker) {
        int maxPower = Integer.MIN_VALUE;
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(board[i][j].power != 0 && !(i == attacker[0] && j == attacker[1])) {
                    maxPower = Math.max(maxPower, board[i][j].power);
                }
            }
        }
        
        if(maxPower == Integer.MIN_VALUE) return null;

        List<Tower> biggestPower = new ArrayList<>();

        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(board[i][j].power != 0) {
                    if(board[i][j].power == maxPower && !(i == attacker[0] && j == attacker[1])) {
                        biggestPower.add(board[i][j]);
                    }
                }                                       
            }
        }

        if(biggestPower.size() == 1) {
            // 1. 공격력 가장 높은 포탑 (1개)
            return new int[]{biggestPower.get(0).x, biggestPower.get(0).y};
        } else {
            // 2. 가장 오래된 공격
            Collections.sort(biggestPower, new Comparator<Tower>() {
                @Override
                public int compare(Tower t1, Tower t2) {
                    return Integer.compare(t1.attackHistory, t2.attackHistory);
                }
            });
            int recentMin = biggestPower.get(0).attackHistory;
            List<Tower> recent = new ArrayList<>();
            for(int i = 0; i < biggestPower.size(); i++) {
                if(biggestPower.get(i).attackHistory == recentMin) {
                    recent.add(biggestPower.get(i));
                }
            }

            if(recent.size() == 1) {
                return new int[]{recent.get(0).x, recent.get(0).y};
            } else {
                // 3. 행 열 합 가장 작은
                Collections.sort(recent, new Comparator<Tower>() {
                    @Override
                    public int compare(Tower t1, Tower t2) {
                        return Integer.compare(t1.x + t1.y, t2.x + t2.y);
                    }
                });
                int rowColSumMax = recent.get(0).x + recent.get(0).y;
                List<Tower> rowColSum = new ArrayList<>();
                for(int i = 0; i < recent.size(); i++) {
                    if(recent.get(i).x + recent.get(i).y == rowColSumMax) {
                        rowColSum.add(recent.get(i));
                    }
                }
                                    
                if(rowColSum.size() == 1) {
                    return new int[]{rowColSum.get(0).x, rowColSum.get(0).y};
                } else {
                    // 4. 열 가장 작은
                    Collections.sort(rowColSum, new Comparator<Tower>() {
                        @Override
                        public int compare(Tower t1, Tower t2) {
                            return Integer.compare(t1.y, t2.y);
                        }
                    });
                    return new int[]{rowColSum.get(0).x, rowColSum.get(0).y};
                }
            }
        }
    } // selectVictim()

    private static void attack(int[] attacker, int[] target) {
        visited = new boolean[n][m];
        Queue<Point> queue = new LinkedList<>();
        List<int[]> tmp = new ArrayList<>();
        tmp.add(new int[]{attacker[0], attacker[1]});
        Point start = new Point(attacker[0], attacker[1], tmp);
        queue.offer(start);
        visited[attacker[0]][attacker[1]] = true;
        Point shortestPathPoint = null;
        while(!queue.isEmpty()) {
            Point current = queue.poll();
            int cx = current.x;
            int cy = current.y;
            // System.out.println("POLL " + current + ": " + cx + ", " + cy);
            if(cx == target[0] && cy == target[1]) {
                // 최단거리 탐색 완료
                shortestPathPoint = current;
                break;
            }
            int[] dx = {0, 1, 0, -1};
            int[] dy = {1, 0, -1, 0};

            for(int i = 0; i < 4; i++) {
                int nx = cx + dx[i];
                int ny = cy + dy[i];
                // 경계 벗어난다면 반대편으로
                if(nx < 0) nx = n-1;
                if(nx >= n) nx = 0;
                if(ny < 0) ny = m-1;
                if(ny >= m) ny = 0;
                if(board[nx][ny].power != 0 && !visited[nx][ny]) {
                    List<int[]> newPath = new ArrayList<>(current.path);
                    newPath.add(new int[]{nx, ny});
                    queue.offer(new Point(nx, ny, newPath));
                    visited[nx][ny] = true;

                    // System.out.println("OFFER " + input + ": " + nx + ", " + ny);
                    visited[nx][ny] = true;
                }
            }
        } // while
        // System.out.println("SHORTEST PATH");
        // if(shortestPathPoint != null ) {
        //         for(int i = 0 ; i < shortestPathPoint.path.size() ; i++) {
        //         System.out.print(shortestPathPoint.path.get(i)[0] + ", " + shortestPathPoint.path.get(i)[1]);
        //         System.out.println();
        //     }
        // }
        // System.out.println();
        if(shortestPathPoint == null) {
            // 포탄 공격
            // print();
            // System.out.println("POTAN");
            potanAttack(attacker, target);
            // print();
        } else {
            // 레이저 공격 로직
            // print();
            // System.out.println("LAZER");
            lazerAttack(attacker, shortestPathPoint);
            // print();
        }
    } // attack(int[], int[])
    private static void lazerAttack(int[] attacker, Point target) {
        // System.out.println("LAZER ATTACK");
        List<int[]> path = target.path;
        int attackerPower = board[attacker[0]][attacker[1]].power;

        for(int i = 0; i < path.size(); i++) {
            int x = path.get(i)[0];
            int y = path.get(i)[1];
            // System.out.println("LAZER :" + x + ", " + y);
            if(i == path.size() - 1) {
                // 공격지점
                // System.out.println(board[x][y].power + " -= " + attackerPower);
                int result = board[x][y].power - attackerPower;
                board[x][y].power = (result < 0 ? 0 : result);
            } else if(i != 0) {
                // 경로
                // System.out.println(board[x][y].power + " -= " + attackerPower/2);
                int result = board[x][y].power - attackerPower/2;
                board[x][y].power = (result < 0 ? 0 : result);
            }
            previousAttack[x][y] = true;
        }
    }
    private static void potanAttack(int[] attacker, int[] target) {
        // System.out.println("POTAN ATTACK");
        int attackPower = board[attacker[0]][attacker[1]].power;
        int targetX = target[0];
        int targetY = target[1];
        int targetAttackResult = board[targetX][targetY].power - attackPower;
        board[targetX][targetY].power = targetAttackResult < 0 ? 0 : targetAttackResult;
        previousAttack[targetX][targetY] = true;
        int[] dx = {-1, -1, -1, 0, 0, 1, 1, 1};
        int[] dy = {-1, 0, 1, -1, 1, -1, 0, 1};
        for(int i = 0; i < dx.length; i++) {
            int nx = targetX + dx[i];
            int ny = targetY + dy[i];

            // 경계 벗어난다면 반대편으로
            if(nx < 0) nx = n - 1;
            if(nx >= n) nx = 0;
            if(ny < 0) ny = m - 1;
            if(ny >= m) ny = 0;

            // 자신은 제외
            if(nx == attacker[0] && ny == attacker[1]) continue;

            int result = board[nx][ny].power - attackPower/2;
            board[nx][ny].power = result < 0 ? 0 : result;
            previousAttack[nx][ny] = true;
        }
    }
    private static void print() {
        for(int i = 0 ; i < board.length ; i++) {
            for(int j = 0 ; j < board[i].length ; j++) {
                System.out.print(board[i][j].power + " ");
            }
            System.out.println();
        }
    }
    private static void repair() {
        // System.out.println("REPAIR");
        for(int i = 0 ; i < board.length ; i++) {
            for(int j = 0 ; j < board[i].length ; j++) {
                if(!previousAttack[i][j]) {
                    if(board[i][j].power != 0) {
                        board[i][j].power += 1;
                    }
                }
            }
        }
        for (int i = 0; i < n; i++) {
            Arrays.fill(previousAttack[i], false);
        }
    }
}

class Tower {
    int x;
    int y;
    int power;
    int attackHistory;
    
    public Tower(int x, int y, int power) {
        this.x = x;
        this.y = y;
        this.power = power;
        this.attackHistory = 0;
    }
}
class Point {
        int x;
        int y;
        List<int[]> path;
        public Point(int x, int y, List<int[]> path) {
            this.x = x;
            this.y = y;
            this.path = path;
        }
    }

 

정답 코드를 보니 내 코드가 얼마나 더러운지 실감이 됐다..

 

Codetree 제공 모범답안 코드

더보기
더보기
import java.util.Scanner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Queue;
import java.util.LinkedList;

class Pair {
    int x, y;

    public Pair(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

// class turret을 정의해 관리합니다.
class Turret implements Comparable<Turret> {
    int x, y, r, p;

    public Turret(int x, int y, int r, int p) {
        this.x = x;
        this.y = y;
        this.r = r;
        this.p = p;
    }

    // turret의 약함, 강함 우선순위에 맞게 정렬함수를 만들어줍니다.
    public int compareTo(Turret t) {
        if(this.p != t.p) return this.p - t.p;
        if(this.r != t.r) return t.r - this.r;
        if(this.x + this.y != t.x + t.y) return (t.x + t.y) - (this.x + this.y);
        return t.y - this.y;
    }
};

public class Main {
    public static final int MAX_N = 10;
    
    public static int[] dx = new int[]{0, 1, 0, -1};
    public static int[] dy = new int[]{1, 0, -1, 0};
    public static int[] dx2 = new int[]{0, 0, 0, -1, -1, -1, 1, 1, 1};
    public static int[] dy2 = new int[]{0, -1, 1, 0, -1, 1, 0, -1, 1};
    
    public static int n, m, k;
    public static int turn;
    
    // 현재 포탑들이 가진 힘과 언제 각성했는지 기록해줍니다.
    public static int[][] board = new int[MAX_N][MAX_N];
    public static int[][] rec = new int[MAX_N][MAX_N];
    
    // 빛의 공격을 할 때 방문 여부와 경로 방향을 기록해줍니다.
    public static boolean[][] vis = new boolean[MAX_N][MAX_N];
    public static int[][] backX = new int[MAX_N][MAX_N];
    public static int[][] backY = new int[MAX_N][MAX_N];
    
    // 공격과 무관했는지 여부를 저장합니다.
    public static boolean[][] isActive = new boolean[MAX_N][MAX_N];
    
    // 살아있는 포탑들을 관리합니다.
    public static ArrayList<Turret> liveTurret = new ArrayList<>();
    
    // 턴을 진행하기 전 필요한 전처리를 정리해줍니다.
    public static void init() {
        turn++;
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++) {
                vis[i][j] = false;
                isActive[i][j] = false;
            }
    }
    
    // 각성을 진행합니다.
    // 각성을 하면 가장 약한 포탑이 n + m만큼 강해집니다.
    public static void awake() {
        // 우선순위에 맞게 현재 살아있는 포탑들을 정렬해줍니다.
        Collections.sort(liveTurret);
    
        // 가장 약한 포탑을 찾아 n + m만큼 더해주고,
        // isActive와 liveTurret 배열도 갱신해줍니다.
        Turret weakTurret = liveTurret.get(0);
        int x = weakTurret.x;
        int y = weakTurret.y;
    
        board[x][y] += n + m;
        rec[x][y] = turn;
        weakTurret.p = board[x][y];
        weakTurret.r = rec[x][y];
        isActive[x][y] = true;
    
        liveTurret.set(0, weakTurret);
    }
    
    // 레이저 공격을 진행합니다.
    public static boolean laserAttack() {
        // 기존에 정렬된 가장 앞선 포탑이
        // 각성한 포탑입니다.
        Turret weakTurret = liveTurret.get(0);
        int sx = weakTurret.x;
        int sy = weakTurret.y;
        int pow = weakTurret.p;
    
        // 기존에 정렬된 가장 뒤 포탑이
        // 각성한 포탑을 제외한 포탑 중 가장 강한 포탑입니다.
        Turret strongTurret = liveTurret.get(liveTurret.size() - 1);
        int ex = strongTurret.x;
        int ey = strongTurret.y;
    
        // bfs를 통해 최단경로를 관리해줍니다.
        Queue<Pair> q = new LinkedList<>();
        vis[sx][sy] = true;
        q.add(new Pair(sx, sy));
    
        // 가장 강한 포탑에게 도달 가능한지 여부를 canAttack에 관리해줍니다.
        boolean canAttack = false;
    
        while(!q.isEmpty()) {
            int x = q.peek().x;
            int y = q.peek().y;
            q.poll();
    
            // 가장 강한 포탑에게 도달할 수 있다면
            // 바로 멈춥니다.
            if(x == ex && y == ey) {
                canAttack = true;
                break;
            }
    
            // 각각 우, 하, 좌, 상 순서대로 방문하며 방문 가능한 포탑들을 찾고
            // queue에 저장해줍니다.
            for(int dir = 0; dir < 4; dir++) {
                int nx = (x + dx[dir] + n) % n;
                int ny = (y + dy[dir] + m) % m;
    
                // 이미 방문한 포탑이라면 넘어갑니다.
                if(vis[nx][ny]) 
                    continue;
    
                // 벽이라면 넘어갑니다.
                if(board[nx][ny] == 0) 
                    continue;
    
                vis[nx][ny] = true;
                backX[nx][ny] = x;
                backY[nx][ny] = y;
                q.add(new Pair(nx, ny));
            }
        }
    
        // 만약 도달 가능하다면 공격을 진행합니다.
        if(canAttack) {
            // 우선 가장 강한 포탑에게는 pow만큼의 공격을 진행합니다.
            board[ex][ey] -= pow;
            if(board[ex][ey] < 0) 
                board[ex][ey] = 0;
            isActive[ex][ey] = true;
    
            // 기존의 경로를 역추적하며
            // 경로 상에 있는 모든 포탑에게 pow / 2만큼의 공격을 진행합니다.
            int cx = backX[ex][ey];
            int cy = backY[ex][ey];
    
            while(!(cx == sx && cy == sy)) {
                board[cx][cy] -= pow / 2;
                if(board[cx][cy] < 0) 
                    board[cx][cy] = 0;
                isActive[cx][cy] = true;
    
                int nextCx = backX[cx][cy];
                int nextCy = backY[cx][cy];
    
                cx = nextCx;
                cy = nextCy;
            }
        }
    
        // 공격을 성공했는지 여부를 반환합니다.
        return canAttack;
    }
    
    // 레이저 공격을 하지 못했다면 폭탄 공격을 진행합니다.
    public static void bombAttack() {
        // 기존에 정렬된 가장 앞선 포탑이
        // 각성한 포탑입니다.
        Turret weakTurret = liveTurret.get(0);
        int sx = weakTurret.x;
        int sy = weakTurret.y;
        int pow = weakTurret.p;
    
        // 기존에 정렬된 가장 뒤 포탑이
        // 각성한 포탑을 제외한 포탑 중 가장 강한 포탑입니다.
        Turret strongTurret = liveTurret.get(liveTurret.size() - 1);
        int ex = strongTurret.x;
        int ey = strongTurret.y;
    
        // 가장 강한 포탑의 3 * 3 범위를 모두 탐색하며
        // 각각에 맞는 공격을 진행합니다.
        for(int dir = 0; dir < 9; dir++) {
            int nx = (ex + dx2[dir] + n) % n;
            int ny = (ey + dy2[dir] + m) % m;
    
            // 각성한 포탑 자기 자신일 경우 넘어갑니다.
            if(nx == sx && ny == sy) 
                continue;
    
            // 가장 강한 포탑일 경우 pow만큼의 공격을 진행합니다.
            if(nx == ex && ny == ey) {
                board[nx][ny] -= pow;
                if(board[nx][ny] < 0) 
                    board[nx][ny] = 0;
                isActive[nx][ny] = true;
            }
            // 그 외의 경우 pow / 2만큼의 공격을 진행합니다.
            else {
                board[nx][ny] -= pow / 2;
                if(board[nx][ny] < 0) 
                    board[nx][ny] = 0;
                isActive[nx][ny] = true;
            }
        }
    }
    
    // 공격에 관여하지 않은 모든 살아있는 포탑의 힘을 1 증가시킵니다.
    public static void reserve() {
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                if(isActive[i][j]) 
                    continue;
                if(board[i][j] == 0) 
                    continue;
                board[i][j]++;
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // 입력:
        n = sc.nextInt();
        m = sc.nextInt();
        k = sc.nextInt();
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                board[i][j] = sc.nextInt();

        // k턴 동안 진행됩니다.
        while(k-- > 0) {
            // 턴을 진행하기 전 살아있는 포탑을 정리합니다.
            liveTurret = new ArrayList<>();
            for(int i = 0; i < n; i++)
                for(int j = 0; j < m; j++)
                    if(board[i][j] > 0) {
                        Turret newTurret = new Turret(i, j, rec[i][j], board[i][j]);
                        liveTurret.add(newTurret);
                    }

            // 살아있는 포탑이 1개 이하라면 바로 종료합니다.
            if(liveTurret.size() <= 1) 
                break;

            // 턴을 진행하기 전 필요한 전처리를 정리해줍니다.
            init();

            // 각성을 진행합니다.
            awake();

            // 레이저 공격을 진행합니다.
            boolean isSuc = laserAttack();
            // 레이저 공격을 하지 못했다면 포탄 공격을 진행합니다.
            if(!isSuc) 
                bombAttack();

            // 공격에 관여하지 않은 모든 살아있는 포탑의 힘을 1 증가시킵니다.
            reserve();
        }

        // 살아있는 포탑의 힘 중 가장 큰 값을 출력합니다.
        int ans = 0;
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                ans = Math.max(ans, board[i][j]);

        Systehttp://m.out.print(ans);
    }
}

디버깅 포인트

1. int dx int dy 경계 처리 잘 하기 (모서리 넘어갈 때)
2. 모든 포탑 공격력 0 시에 턴 더 진행하지 않고 턴 종료
3. 레이저 공격 루트에서 공격자 제외
4. 공격 루트 넣어줄 때 그냥 새로운 리스트 객체 만들기 (재사용하면 참조 어긋날 수 있음)

6. 객체 비교할 일이 있을 때는 Comparable 정의하기.. (인생이 편해질듯)

// class turret을 정의해 관리합니다.
class Turret implements Comparable<Turret> {
    int x, y, r, p;

    public Turret(int x, int y, int r, int p) {
        this.x = x;
        this.y = y;
        this.r = r;
        this.p = p;
    }

    // turret의 약함, 강함 우선순위에 맞게 정렬함수를 만들어줍니다.
    public int compareTo(Turret t) {
        if(this.p != t.p) return this.p - t.p;
        if(this.r != t.r) return t.r - this.r;
        if(this.x + this.y != t.x + t.y) return (t.x + t.y) - (this.x + this.y);
        return t.y - this.y;
    }
};

 

 

728x90
반응형

'PS' 카테고리의 다른 글

[Codetree] 마법의 숲 탐색  (0) 2025.04.11
[Codetree] 왕실의 기사 대결  (0) 2025.04.10