문제 링크

성능 요약

메모리: 205512 KB, 시간: 456 ms

 

분류

백트래킹, 깊이 우선 탐색, 그래프 이론, 그래프 탐색

 

문제 설명

BOJ 알고리즘 캠프에는 총 N명이 참가하고 있다. 사람들은 0번부터 N-1번으로 번호가 매겨져 있고, 일부 사람들은 친구이다.

오늘은 다음과 같은 친구 관계를 가진 사람 A, B, C, D, E가 존재하는지 구해보려고 한다.

  • A는 B와 친구다.
  • B는 C와 친구다.
  • C는 D와 친구다.
  • D는 E와 친구다.

위와 같은 친구 관계가 존재하는지 안하는지 구하는 프로그램을 작성하시오.

 

입력

첫째 줄에 사람의 수 N (5 ≤ N ≤ 2000)과 친구 관계의 수 M (1 ≤ M ≤ 2000)이 주어진다.

둘째 줄부터 M개의 줄에는 정수 a와 b가 주어지며, a와 b가 친구라는 뜻이다. (0 ≤ a, b ≤ N-1, a ≠ b) 같은 친구 관계가 두 번 이상 주어

지는 경우는 없다.

 

출력

문제의 조건에 맞는 A, B, C, D, E가 존재하면 1을 없으면 0을 출력한다.

 

풀이 (DFS)

시간 복잡도 O(N) 공간 복잡도 O(N)

아이디어: 문제에서는 A-B-C-D-E의 관계가 존재하는지 묻고 있기에 이는 곧 그래프 정보에서 깊이가 5인 구간이 존재하는지 여부를 묻는 다고 볼 수 있다. 따라서 DFS(깊이 우선 탐색)을 사용하여 만약 5이상의 깊이를 가지는 구간이 존재한다면 1을 출력시키고 프로그램을 종료시키면 된다.

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

public class Main {
    static HashMap<Integer, ArrayList<Integer>> map;
    static boolean[] visited;
    static int depth;
    public static boolean dfs(int node){
        depth++;
        // 깊이가 5이면 문제에서 원하는 관계가 존재하기에 true
        if(depth ==5) return true;
        // 현재 노드를 방문한 것으로 표시
        visited[node] = true;

        // 현재 노드에 인접한 모든 노드를 확인
        ArrayList<Integer> nextNodes = map.get(node);
        for (Integer nextNode : nextNodes) {
            if(!visited[nextNode])
                if(dfs(nextNode))
                    return true;
        }
        // 깊이 감소
        depth--;
        visited[node] = false; // 방문 초기화
        return false;
    }
    public static int sti(StringTokenizer st){
        return Integer.parseInt(st.nextToken());
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = sti(st), M = sti(st);
        map = new HashMap<>();
        for (int i = 0; i < N; i++) map.put(i,new ArrayList<>());
        boolean ans = false;

        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            int op1 = sti(st), op2 = sti(st);
            // 같은 관계는 두번 이상 주어지지 않는다.
            map.get(op1).add(op2);
            map.get(op2).add(op1);
        }
        // 깊이가 5면 1 아니면 0
        for (int i = 0; i < N; i++) {
            visited = new boolean[N];
            depth = 0;
            if (dfs(i)) {
                System.out.println(1);
                return;
            }
        }
        System.out.println(0);
    }
}

참고하여 개선된 풀이 (DFS)

아이디어: static HashMap<Integer, ArrayList<Integer>> map; 자료구조를 단순한 연결 리스트 형식의 자료구조로 변경함으로써 버킷배열, 해시 함수, 재해시 연산을 줄임으로서 오버헤드를 줄여 메모리와 속도를 둘다 개선

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

public class Main {
    static int answer = 0;
    static boolean[] visited;
    static Node[] map;
    static class Node{
        int vertex;
        Node link;

        public Node(int vertex, Node link) {
            this.vertex = vertex;
            this.link = link;
        }
    }
    static void dfs(int index, int depth){
        visited[index] = true;
        if(depth==5){
            answer =1;
            return;
        }
        for(Node next = map[index]; next!=null; next=next.link){
            if(!visited[next.vertex])
                dfs(next.vertex,depth+1);
        }
        visited[index] = false;
    }
    public static int parseTokenToInt(StringTokenizer st){
        return Integer.parseInt(st.nextToken());
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N =  parseTokenToInt(st), M =  parseTokenToInt(st);
        map = new Node[N];
        visited = new boolean[N];
        int head, next;

        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            head =  parseTokenToInt(st);
            next =  parseTokenToInt(st);
            // 같은 관계는 두번 이상 주어지지 않는다.
            map[head] = new Node(next, map[head]);
            map[next] = new Node(head,map[next]);
        }
        // 깊이가 5면 1 아니면 0
        for (int i = 0; i < N; i++) {
            dfs(i,1);
        }
        System.out.println(answer);
    }
}

 

'PS > Gold' 카테고리의 다른 글

[Gold V] 숫자고르기 - 2668 (Java)  (0) 2023.08.21
[Gold IV] 연구소 - 14502 (Java)  (0) 2023.08.20
[Gold IV] 치즈 - 2636 (Java)  (0) 2023.08.20
[Gold V] 공주님을 구해라! - 17836 (Java)  (0) 2023.08.17
[Gold V] 숨바꼭질 3 - 13549 (Java)  (0) 2023.08.16

문제 링크

성능 요약

메모리: 14244 KB, 시간: 124 ms

 

분류

깊이 우선 탐색, 그래프 이론, 그래프 탐색

 

문제 설명

세로 두 줄, 가로로 N개의 칸으로 이루어진 표가 있다. 첫째 줄의 각 칸에는 정수 1, 2, …, N이 차례대로 들어 있고 둘째 줄의 각 칸에는 1이상 N이하인 정수가 들어 있다. 첫째 줄에서 숫자를 적절히 뽑으면, 그 뽑힌 정수들이 이루는 집합과, 뽑힌 정수들의 바로 밑의 둘째 줄에 들어있는 정수들이 이루는 집합이 일치한다. 이러한 조건을 만족시키도록 정수들을 뽑되, 최대로 많이 뽑는 방법을 찾는 프로그램을 작성하시오. 예를 들어, N=7인 경우 아래와 같이 표가 주어졌다고 하자.

이 경우에는 첫째 줄에서 1, 3, 5를 뽑는 것이 답이다. 첫째 줄의 1, 3, 5밑에는 각각 3, 1, 5가 있으며 두 집합은 일치한다. 이때 집합의 크기는 3이다. 만약 첫째 줄에서 1과 3을 뽑으면, 이들 바로 밑에는 정수 3과 1이 있으므로 두 집합이 일치한다. 그러나, 이 경우에 뽑힌 정수의 개수는 최대가 아니므로 답이 될 수 없다.

 

입력

첫째 줄에는 N(1≤N≤100)을 나타내는 정수 하나가 주어진다. 그 다음 줄부터는 표의 둘째 줄에 들어가는 정수들이 순서대로 한 줄에 하나씩 입력된다.

 

출력

첫째 줄에 뽑힌 정수들의 개수를 출력하고, 그 다음 줄부터는 뽑힌 정수들을 작은 수부터 큰 수의 순서로 한 줄에 하나씩 출력한다.

 

풀이 (DFS)

시간 복잡도 O(n) 공간 복잡도 O(n)

아이디어: DFS 탐색을 활용하여 만약 다음 노드를 방문하지 않았다면 탐색 이후 예전에 방문 한 적이 있으면서 끝에 해당 하는 노드라면 사이클이 생성되었다고 판단하여 해당 사이클의 요소들을 결과를 저장하는 배열에 추가 하여 구현

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    static int N;
    static int[] graph;
    static boolean[] visited;
    static boolean[] finished;
    static List<Integer> result;

    public static void dfs(int current) {
        visited[current] = true;
        // 저장된 그래프 정보를 토대로 다음 노드 구하기
        int next = graph[current];

        // 만약 다음 노드가 방문되지 않은 노드라면 dfs 탐색
        if (!visited[next]) {
            dfs(next);
        } else if (!finished[next]) {
            for (int i = next; i != current; i = graph[i]) {
                result.add(i);
            }
            result.add(current);
        }
        
        // DFS로 방문한 노드를 사이클의 끝이 될 수 있는 부분으로 체크
        finished[current] = true;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        N = Integer.parseInt(br.readLine());
        graph = new int[N + 1];
        visited = new boolean[N + 1];
        finished = new boolean[N + 1];
        result = new ArrayList<>();

        for (int i = 1; i <= N; i++) {
            graph[i] = Integer.parseInt(br.readLine());
        }

        for (int i = 1; i <= N; i++) {
            if (!visited[i]) dfs(i);
        }

        Collections.sort(result);
        System.out.println(result.size());
        for (int num : result) {
            System.out.println(num);
        }
    }
}

'PS > Gold' 카테고리의 다른 글

[Gold V] ABCDE - 13023 (Java)  (0) 2023.08.23
[Gold IV] 연구소 - 14502 (Java)  (0) 2023.08.20
[Gold IV] 치즈 - 2636 (Java)  (0) 2023.08.20
[Gold V] 공주님을 구해라! - 17836 (Java)  (0) 2023.08.17
[Gold V] 숨바꼭질 3 - 13549 (Java)  (0) 2023.08.16

문제 링크

성능 요약

메모리: 102100 KB, 시간: 460 ms

 

분류

너비 우선 탐색, 브루트포스 알고리즘, 그래프 이론, 그래프 탐색, 구현

 

문제 설명

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다.

연구소는 크기가 N×M인 직사각형으로 나타낼 수 있으며, 직사각형은 1×1 크기의 정사각형으로 나누어져 있다. 연구소는 빈 칸, 벽으로 이루어져 있으며, 벽은 칸 하나를 가득 차지한다.

일부 칸은 바이러스가 존재하며, 이 바이러스는 상하좌우로 인접한 빈 칸으로 모두 퍼져나갈 수 있다. 새로 세울 수 있는 벽의 개수는 3개이며, 꼭 3개를 세워야 한다.

예를 들어, 아래와 같이 연구소가 생긴 경우를 살펴보자.

2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

이때, 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 곳이다. 아무런 벽을 세우지 않는다면, 바이러스는 모든 빈 칸으로 퍼져나갈 수 있다.

2행 1열, 1행 2열, 4행 6열에 벽을 세운다면 지도의 모양은 아래와 같아지게 된다.

2 1 0 0 1 1 0
1 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 1 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

바이러스가 퍼진 뒤의 모습은 아래와 같아진다.

2 1 0 0 1 1 2
1 0 1 0 1 2 2
0 1 1 0 1 2 2
0 1 0 0 0 1 2
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

벽을 3개 세운 뒤, 바이러스가 퍼질 수 없는 곳을 안전 영역이라고 한다. 위의 지도에서 안전 영역의 크기는 27이다.

연구소의 지도가 주어졌을 때 얻을 수 있는 안전 영역 크기의 최댓값을 구하는 프로그램을 작성하시오.

 

입력

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. (3 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에 지도의 모양이 주어진다. 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 위치이다. 2의 개수는 2보다 크거나 같고, 10보다 작거나 같은 자연수이다.

빈 칸의 개수는 3개 이상이다.

 

출력

첫째 줄에 얻을 수 있는 안전 영역의 최대 크기를 출력한다.

 

풀이 (BFS+Bruth Force)

시간 복잡도: O(N*M^2*C) C는 M*N 개의 숫자 중 3개를 뽑는 경우의 수

공간 복잡도: O(N*M) 

아이디어:  3개에 공간에 벽을 세우는 모든 경우에서 안전한 영역이 만들어지는 모든 경우의 수를 모두 비교 하여 값을 출력

1단계: 0인 공간 중에 중복을 포함하지 않고 순서를 생각하지 않으며 3곳을 뽑아 벽을 세워 미로를 새로 만든다.

2단계: 2인 공간에서 BFS 탐색을 시작하여 만들어지는 안전 공간의 수를 구하고 최신화 한다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.StringTokenizer;

class Pos{
    int c,r;

    public Pos(int c, int r) {
        this.c = c;
        this.r = r;
    }
}

public class Main {
    static int[][] MAP;
    static boolean[][] visited;
    static int[][] ways = {{1,0},{-1,0},{0,1},{0,-1}};
    static int N,M;
    public static int bfs(int[][]map, int c,int r){
        int count = 0;
        Deque<Pos> deque = new ArrayDeque<>();
        deque.add(new Pos(c,r));

        while(!deque.isEmpty()){
            Pos cur = deque.pollFirst();
            for (int[] way : ways) {
                int nc = cur.c + way[0];
                int nr = cur.r + way[1];
                if(nc<0||nc>=N||nr<0||nr>=M||map[nc][nr]!=0||visited[nc][nr])
                    continue;

                // 0인 경우에만 추가
                visited[nc][nr] = true;
                map[nc][nr] = 2;
                count++;
                deque.add(new Pos(nc,nr));
            }
        }
        return count;
    }
    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());
        MAP = new int[N][M];

        int ans = 0;
        int emptySpace = 0;
        ArrayList<Pos> gasPoses = new ArrayList<>();
        ArrayList<Pos> emptyPoses = new ArrayList<>();

        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());
                if(MAP[i][j]==0) {
                    emptySpace++;
                    emptyPoses.add(new Pos(i,j));
                }
                if(MAP[i][j]==2) gasPoses.add(new Pos(i,j));
            }
        }

        for(int i=0;i<emptyPoses.size()-2;i++){
            for (int j = i+1; j <emptyPoses.size()-1 ; j++) {
                for (int k = j+1; k <emptyPoses.size(); k++) {
                    Pos first = emptyPoses.get(i);
                    Pos second = emptyPoses.get(j);
                    Pos third = emptyPoses.get(k);

                    int[][] copyMap = new int[MAP.length][];
                    for (int m = 0; m < MAP.length; m++) {
                        copyMap[m] = MAP[m].clone();
                    }

                    copyMap[first.c][first.r] = 1;
                    copyMap[second.c][second.r] = 1;
                    copyMap[third.c][third.r] = 1;
                    // 모든 벽을 세운 로직 추가
                    visited = new boolean[N][M];
                    int smokingArea = 0;
                    for (Pos target : gasPoses) {
                        if(!visited[target.c][target.r])
                            smokingArea += bfs(copyMap, target.c,target.r);
                    }
                    // 3개의 벽이 추가됬기 때문에 -3
                    ans = Math.max(emptySpace-3-smokingArea,ans);

                }
            }
        }

        System.out.println(ans);
    }
}

'PS > Gold' 카테고리의 다른 글

[Gold V] ABCDE - 13023 (Java)  (0) 2023.08.23
[Gold V] 숫자고르기 - 2668 (Java)  (0) 2023.08.21
[Gold IV] 치즈 - 2636 (Java)  (0) 2023.08.20
[Gold V] 공주님을 구해라! - 17836 (Java)  (0) 2023.08.17
[Gold V] 숨바꼭질 3 - 13549 (Java)  (0) 2023.08.16

 

문제 링크

성능 요약

메모리: 18484 KB, 시간: 204 ms

분류

너비 우선 탐색, 그래프 이론, 그래프 탐색, 구현, 시뮬레이션

 

문제 설명

아래 <그림 1>과 같이 정사각형 칸들로 이루어진 사각형 모양의 판이 있고, 그 위에 얇은 치즈(회색으로 표시된 부분)가 놓여 있다. 판의 가장자리(<그림 1>에서 네모 칸에 X친 부분)에는 치즈가 놓여 있지 않으며 치즈에는 하나 이상의 구멍이 있을 수 있다.

이 치즈를 공기 중에 놓으면 녹게 되는데 공기와 접촉된 칸은 한 시간이 지나면 녹아 없어진다. 치즈의 구멍 속에는 공기가 없지만 구멍을 둘러싼 치즈가 녹아서 구멍이 열리면 구멍 속으로 공기가 들어가게 된다. <그림 1>의 경우, 치즈의 구멍을 둘러싼 치즈는 녹지 않고 ‘c’로 표시된 부분만 한 시간 후에 녹아 없어져서 <그림 2>와 같이 된다.

<그림 1> 원래 치즈 모양

다시 한 시간 후에는 <그림 2>에서 ‘c’로 표시된 부분이 녹아 없어져서 <그림 3>과 같이 된다.

<그림 2> 한 시간 후의 치즈 모양

<그림 3> 두 시간 후의 치즈 모양

<그림 3>은 원래 치즈의 두 시간 후 모양을 나타내고 있으며, 남은 조각들은 한 시간이 더 지나면 모두 녹아 없어진다. 그러므로 처음 치즈가 모두 녹아 없어지는 데는 세 시간이 걸린다. <그림 3>과 같이 치즈가 녹는 과정에서 여러 조각으로 나누어 질 수도 있다.

입력으로 사각형 모양의 판의 크기와 한 조각의 치즈가 판 위에 주어졌을 때, 공기 중에서 치즈가 모두 녹아 없어지는 데 걸리는 시간과 모두 녹기 한 시간 전에 남아있는 치즈조각이 놓여 있는 칸의 개수를 구하는 프로그램을 작성하시오.

 

입력

첫째 줄에는 사각형 모양 판의 세로와 가로의 길이가 양의 정수로 주어진다. 세로와 가로의 길이는 최대 100이다. 판의 각 가로줄의 모양이 윗 줄부터 차례로 둘째 줄부터 마지막 줄까지 주어진다. 치즈가 없는 칸은 0, 치즈가 있는 칸은 1로 주어지며 각 숫자 사이에는 빈칸이 하나씩 있다.

 

출력

첫째 줄에는 치즈가 모두 녹아서 없어지는 데 걸리는 시간을 출력하고, 둘째 줄에는 모두 녹기 한 시간 전에 남아있는 치즈조각이 놓여 있는 칸의 개수를 출력한다.

 

풀이 (BFS) 

시간 복잡도 O(N*M) 공간 복잡도 O(N*M)

아이디어: 2가지 로직을 사용(1. 내부의 공기(0)와 외부 공기(2)로 구분짓기, 2. 치즈녹이기)

1: 2번 작업을 하기전에 0,0 제일 우측 공간부터 시작하여 BFS를 사용하여 전체 탐색으로 내부 공기와 외부 공기를 구분짓는다.

2: Map[i][j]가 1이라면 BFS를 사용하여 전체 탐색 치즈(1) 주변에 외부 공기(2)가 있다면 해당 위치를 따로 저장하고 이후

탐색이 종료된다면 저장된 공간을 전부 공기(2)로 바꿔준다.   

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

class Pos{
    int c,r;
    Pos(int c,int r){
        this.c=c;
        this.r=r;
    }
}

public class Main {
    public static int sti(StringTokenizer st){
        return Integer.parseInt(st.nextToken());
    }
    public static long stl(StringTokenizer st){
        return Long.parseLong(st.nextToken());
    }
    static int[][] M;
    static boolean[][] visited;
    static boolean[][] isTarget;
    static int[][] ways = {{1,0},{-1,0},{0,1},{0,-1}};
    static ArrayList<Pos> targets;
    static int remain;
    public static boolean isOut(int C,int R){
        return C<0||C>=M.length||R<0||R>=M[0].length;
    }
    public static void judgeAir(int c, int r){
        Deque<Pos> q = new ArrayDeque<>();
        q.add(new Pos(c, r));
        while (!q.isEmpty()) {
            Pos cur = q.poll();
            for (int[] way : ways) {
                int C = way[0] + cur.c;
                int R = way[1] + cur.r;
                if (isOut(C, R) || M[C][R] == 1 || visited[C][R]) continue;
                M[C][R] = 2; // 외부 공기 표시
                q.add(new Pos(C, R));
                visited[C][R] = true;
            }
        }
    }
    public static void bfs(int c,int r){
        Deque<Pos> q = new ArrayDeque<>();
        targets = new ArrayList<>();
        q.add(new Pos(c,r));

        while(!q.isEmpty()){
            Pos cur = q.poll();

            for(int[] way: ways){
                int C = way[0]+cur.c;
                int R = way[1]+cur.r;
                if(isOut(C,R)) continue;

                if(!isOut(C,R)&&!visited[C][R]) {
                    q.add(new Pos(C, R));
                    visited[C][R] = true;
                }
                // 이미 검사하는 칸이 들어가있다면 다음 반복으로
                if(isTarget[cur.c][cur.r]) continue;

                // 현재가 공기이고 이동 하기 전 위치가 치즈인 경우
                if(M[cur.c][cur.r]==1&&M[C][R]==2&&!isTarget[cur.c][cur.r]) {
                   targets.add(new Pos(cur.c,cur.r));
                   isTarget[cur.c][cur.r] = true;
                }

            }
        }

        remain = targets.size();
        // 적용
        for (Pos target : targets) {
             M[target.c][target.r] = 2;
        }

    }
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int C = sti(st), R = sti(st);
        M = new int[C][R];
        isTarget = new boolean[C][R];
        int time = 0;

        for (int i = 0; i < C; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < R; j++) {
                M[i][j] = sti(st);
            }
        }

        for (int i = 0; i < C; i++) {
            for (int j = 0; j < R; j++) {
                if(M[i][j]==1) {
                    visited = new boolean[C][R];
                    judgeAir(0,0);
                    visited = new boolean[C][R];
                    visited[i][j] = true;
                    bfs(i, j);
                    time++;
                }
            }
        }
        System.out.println(time);
        System.out.println(remain);

    }
}

 

개선된 풀이 (BFS) 

차이점: 위에 풀이 방식은 M[][]을 무조건 적으로 전체 탐색하여 녹일 치즈를 따로 배열에 저장하고 탐색이 끝난 후에 반영하는 것을 M[][]에 치즈가 발견된다면 계속 반복하는 방식인 반면

아래의 풀이는 녹여야하는 치즈의 숫자와 위치 정보를 파악하고 해당 부분의 주변만 탐색하여 다음 반복에 처리해야할 치즈의 정보를 저장하고 외부공기로 전환되는 내부공기를 찾아내 처리하는 방식을 반복함으로써 최소한의 탐색을 통해 문제를 처리하고 있다. 

시간 복잡도 O(N*M) 공간 복잡도 O(N*M)

아이디어: 2가지 로직을 사용(1. 내부의 공기(0)와 외부 공기(2)로 구분지으면서 녹일 치즈 정보 저장하기, 2. 치즈 녹이기, 치즈 정보 저장)

1: BFS를 사용하여 전체 탐색으로 내부 공기와 외부 공기를 구분지음과 동시에 공기와 맞닿아 녹여져야하는 치즈를 따로 큐에 저장한다.

2: 녹여야되는 수 만큼의 치즈만큼 반복하여 치즈를 녹임과 동시에 BFS를 사용하여 탐색하여 녹여진 치즈와 맞닿은 내부 공기가 있다면 1번 작업을 통해 외부공기로 바꾸어주고, 다음 반복에 녹일 치즈의 정보를 저장한다.

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

public class Main {
    public static int sti(StringTokenizer st){
        return Integer.parseInt(st.nextToken());
    }
    public static long stl(StringTokenizer st){
        return Long.parseLong(st.nextToken());
    }
    static int[][] map;
    static boolean[][] visited;
    static Queue<int[]> cq = new ArrayDeque<int[]>();
    static int[][] ways = {{1,0},{-1,0},{0,1},{0,-1}};
    public static boolean isOut(int C,int R){
        return C<0||C>=map.length||R<0||R>=map[0].length;
    }
    public static void judgeAir(int c, int r){
        Queue<int[]> q = new ArrayDeque<>();
        q.add(new int[]{c,r});
        map[c][r] = 2;

        while(!q.isEmpty()){
            int[] cur = q.poll();

            for(int[] way: ways){
                int C = way[0]+cur[0];
                int R = way[1]+cur[1];
                if(isOut(C,R)) continue;

                if(map[C][R]==0){
                    map[C][R]=2;
                    q.add(new int[]{C,R});
                }

                // 외부 공기와 닿은 치즈의 위치 삽입
                if(!visited[C][R]&&map[C][R]==1) {
                    cq.add(new int[]{C,R});
                    visited[C][R] = true;
                }
            }
        }
    }
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int C = sti(st), R = sti(st);
        map = new int[C][R];
        visited = new boolean[C][R];

        for (int i = 0; i < C; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < R; j++) {
                map[i][j] = sti(st);
            }
        }
        int time = 0;
        int rec = 0;
        judgeAir(0,0);

        while(!cq.isEmpty()){
            time++;
            int size = cq.size();
            rec = size;

            // 삽입된 치즈의 수 만큼 반복
            for (;size>0;size--){
                int[] cur = cq.poll();
                // 치즈가 없는 경우는 없으므로 Null 포인트 x 치즈 녹이기
                map[cur[0]][cur[1]] = 2;

                for (int[] way : ways) {
                    int nC = way[0]+cur[0];
                    int nR = way[1]+cur[1];
                    if(isOut(nC,nR)) continue;
                    // 내부 공기가 녹아야 하는 부분과 맞닿아 있다면 외부 공기 처리
                    if(map[nC][nR]==0)
                        judgeAir(nC,nR);
                    // 다음 반복에 사용될 치즈 추가하기
                    else if(!visited[nC][nR]&&map[nC][nR]==1){
                        cq.add(new int[]{nC,nR});
                        visited[nC][nR] = true;
                    }
                }
            }
        }
        System.out.println(time + "\n" + rec);
    }
}

문제 링크

분류

너비 우선 탐색, 그래프 이론, 그래프 탐색

 

문제 설명

용사는 마왕이 숨겨놓은 공주님을 구하기 위해 (N, M) 크기의 성 입구 (1,1)으로 들어왔다. 마왕은 용사가 공주를 찾지 못하도록 성의 여러 군데 마법 벽을 세워놓았다. 용사는 현재의 가지고 있는 무기로는 마법 벽을 통과할 수 없으며, 마법 벽을 피해 (N, M) 위치에 있는 공주님을 구출해야만 한다.

마왕은 용사를 괴롭히기 위해 공주에게 저주를 걸었다. 저주에 걸린 공주는 T시간 이내로 용사를 만나지 못한다면 영원히 돌로 변하게 된다. 공주님을 구출하고 프러포즈 하고 싶은 용사는 반드시 T시간 내에 공주님이 있는 곳에 도달해야 한다. 용사는 한 칸을 이동하는 데 한 시간이 걸린다. 공주님이 있는 곳에 정확히 T시간만에 도달한 경우에도 구출할 수 있다. 용사는 상하좌우로 이동할 수 있다.

성에는 이전 용사가 사용하던 전설의 명검 "그람"이 숨겨져 있다. 용사가 그람을 구하면 마법의 벽이 있는 칸일지라도, 단숨에 벽을 부수고 그 공간으로 갈 수 있다. "그람"은 성의 어딘가에 반드시 한 개 존재하고, 용사는 그람이 있는 곳에 도착하면 바로 사용할 수 있다. 그람이 부술 수 있는 벽의 개수는 제한이 없다.

우리 모두 용사가 공주님을 안전하게 구출 할 수 있는지, 있다면 얼마나 빨리 구할 수 있는지 알아보자.

 

입력

첫 번째 줄에는 성의 크기인 N, M 그리고 공주에게 걸린 저주의 제한 시간인 정수 T가 주어진다. 첫 줄의 세 개의 수는 띄어쓰기로 구분된다. (3 ≤ N, M ≤ 100, 1 ≤ T ≤ 10000)

두 번째 줄부터 N+1번째 줄까지 성의 구조를 나타내는 M개의 수가 띄어쓰기로 구분되어 주어진다. 0은 빈 공간, 1은 마법의 벽, 2는 그람이 놓여있는 공간을 의미한다. (1,1)과 (N,M)은 0이다.

 

출력

용사가 제한 시간 T시간 이내에 공주에게 도달할 수 있다면, 공주에게 도달할 수 있는 최단 시간을 출력한다.

만약 용사가 공주를 T시간 이내에 구출할 수 없다면, "Fail"을 출력한다.

 

풀이 (BFS)

시간 복잡도: O(N*M) 공간 복잡도 O(N*M)

아이디어: 성검 그람에 도착시 탐색을 종료하고 공주님의 방과의 거리를 (N-1-y)+(M-1-x)+time+1을 통해 한번에 계산하고

HashSet과 방문한 곳을 1로 바꾸어서 처리하는 방식을 통해 visited[][] 공간을 사용하지 않고도 중복된 공간을 들어가지 않도록 구현

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

public class Main {
    static class Pos {
        int x, y;

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

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Pos pos = (Pos) o;
            return x == pos.x && y == pos.y;
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y);
        }
    }

    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int M = Integer.parseInt(st.nextToken());
        int T = Integer.parseInt(st.nextToken());
        int[][] map = new int[N][M];
        int ans = Integer.MAX_VALUE;

        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < M; j++) {
               int n = Integer.parseInt(st.nextToken());
                map[i][j] = n;
            }
        }

        int[][] ways = {{1,0},{-1,0},{0,1},{0,-1}};
        int time = -1;
        Queue<HashSet<Pos>> queue = new LinkedList<>();
        HashSet<Pos> node = new HashSet<>();
        node.add(new Pos(0,0));
        queue.add(node);
        while(!queue.isEmpty()){
            HashSet<Pos> curs = queue.poll();
            node = new HashSet<>();
            time++;
            for (Pos cur : curs) {
                map[cur.y][cur.x] = 1;
                for (int[] way : ways) {
                    int y = cur.y+way[0];
                    int x = cur.x+way[1];
                    if(y==N-1&&x==M-1)
                        ans = Math.min(ans,time+1);
                    if(x<0||x>=M||y<0||y>=N||map[y][x]==1)
                        continue;
                    // 성검 사용
                    if(map[y][x] == 2)
                        ans = Math.min(M-1-x+N-1-y+time+1,ans);
                    node.add(new Pos(x,y));
                }
            }
            if(node.size()>=1)
                queue.add(node);

        }

        if(ans<=T)
            System.out.println(ans);
        else
            System.out.println("Fail");
    }
}

답은 맞았지만 해당 방식의 문제점 

즉 HashSet에 데이터 추가, 조회에는 시간복잡도 상으로 O(1)이 소요되지만 생각보다 느릴 수 가 있어 시간적으로 비효율적이며

HashSet은 내부적으로 해시 테이블을 사용하기에, 이는 각 요소를 저장할 때 추가적인 메타데이터(예: 해시 코드, 연결 리스트 노드 등)를 필요로 한다. 따라서 boolean 배열보다 공간적으로 비효율적일 수 있다.

 

개선된 풀이

import javax.print.DocFlavor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

class Pos{
    int x,y,t;
    Pos(int x,int y, int t){
       this.x = x; this.y = y; this.t=t;
    }
}

public class Main {
    static int ans = Integer.MAX_VALUE;
    public static int sti(StringTokenizer st){
        return Integer.parseInt(st.nextToken());
    }
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = sti(st), M = sti(st), T = sti(st);
        int[][] map = new int[N][M];
        boolean[][] visited = new boolean[N][M];

        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < M; j++) {
                map[i][j] = sti(st);
            }
        }

        int[] dy = {1,-1,0,0};
        int[] dx = {0,0,1,-1};
        // Queue<Pos> q = new LinkedList<>();
        // 유의미한 차이는 없지만 메모리 오버헤드가 더 적음
        ArrayDeque<Pos> q = new ArrayDeque<>();
        q.add(new Pos(0,0,0));
        while(!q.isEmpty()){
            Pos cur = q.pollFirst();
            int x = cur.x, y = cur.y, t = cur.t;
            if(map[y][x]==2)
                ans = Math.min((N-1-y)+(M-1-x)+t,ans);
            if(y==N-1&&x==M-1)
                ans = Math.min(ans,t);
            for (int i = 0; i < 4; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                if (nx < 0 || nx >= M || ny < 0 || ny >= N ||
                        map[ny][nx] == 1 || visited[ny][nx])
                    continue;
                visited[ny][nx] = true;
                q.add(new Pos(nx, ny, t + 1));
            }
        }
        System.out.println(ans>T ? "Fail":ans);
    }
}

'PS > Gold' 카테고리의 다른 글

[Gold IV] 연구소 - 14502 (Java)  (0) 2023.08.20
[Gold IV] 치즈 - 2636 (Java)  (0) 2023.08.20
[Gold V] 숨바꼭질 3 - 13549 (Java)  (0) 2023.08.16
[Gold V] 토마토 - 7569 (Java)  (0) 2023.08.15
[Gold IV] 숨바꼭질 4 - 13913 (Java)  (0) 2023.08.04

 

문제 링크

분류

0-1 너비 우선 탐색, 너비 우선 탐색, 데이크스트라, 그래프 이론, 그래프 탐색

 

문제 설명

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일 때 걷는다면 1초 후에 X-1 또는 X+1로 이동하게 된다. 순간이동을 하는 경우에는 0초 후에 2*X의 위치로 이동하게 된다.

수빈이와 동생의 위치가 주어졌을 때, 수빈이가 동생을 찾을 수 있는 가장 빠른 시간이 몇 초 후인지 구하는 프로그램을 작성하시오.

 

입력

첫 번째 줄에 수빈이가 있는 위치 N과 동생이 있는 위치 K가 주어진다. N과 K는 정수이다.

 

출력

수빈이가 동생을 찾는 가장 빠른 시간을 출력한다.

 

풀이 (BFS)

시간 복잡도 O(N) 공간 복잡도 O(N) N: 1000001 (배열의 최대 길이)

아이디어: 기존의 숨바꼭질 문제에서 텔레포트 하는 로직의 시간이 0이 되기 때문에 한 사이클에서 한 번만 동작하는게 아닌 배열의 최대 길이의 근접 할 때까지 현재 위치에서 텔레포트를 함을 처리하고 한칸 뒤로 한칸 앞으로 가는 로직을 처리해야 한다.

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

public class Main {
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int K = Integer.parseInt(st.nextToken());
        if(N==K){
            System.out.println(0);
            return;
        }
        int[] map = new int[100001];
        Arrays.fill(map,-1);
        Queue<ArrayList<int[]>> queue = new LinkedList<>();
        ArrayList<int[]> node = new ArrayList<>();
        node.add(new int[]{N,0});
        map[N] = 0;
        queue.add(node);
        while(!queue.isEmpty()){
            ArrayList<int[]> cur = queue.poll();
            ArrayList<int[]> nextNode = new ArrayList<>();
            for (int[] ints : cur) {
                int time = 1+ints[1];
                int pos = ints[0];
                int tmp = pos;
                while(tmp*2<=100000&&map[tmp*2]==-1){
                    tmp*=2;
                    map[tmp] = time-1;
                    nextNode.add(new int[]{tmp,time-1});
                }
                if(pos-1>=0&&map[pos-1]==-1) {
                    map[pos-1] = time;
                    nextNode.add(new int[]{pos-1,time});
                }
                if(pos+1<=100000&&map[pos+1]==-1) {
                    map[pos+1] = time;
                    nextNode.add(new int[]{pos+1,time});
                }

                if(pos-1==K||pos+1==K||tmp==K){
                    System.out.println(map[K]);
                    return;
                }
            }
            queue.add(nextNode);
        }
        System.out.println(-1);
    }
}

 

'PS > Gold' 카테고리의 다른 글

[Gold IV] 치즈 - 2636 (Java)  (0) 2023.08.20
[Gold V] 공주님을 구해라! - 17836 (Java)  (0) 2023.08.17
[Gold V] 토마토 - 7569 (Java)  (0) 2023.08.15
[Gold IV] 숨바꼭질 4 - 13913 (Java)  (0) 2023.08.04
[Gold V] 토마토 - 7576 (Java)  (0) 2023.08.02

문제 링크

분류

너비 우선 탐색, 그래프 이론, 그래프 탐색

 

문제 설명

철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자모양 상자의 칸에 하나씩 넣은 다음, 상자들을 수직으로 쌓아 올려서 창고에 보관한다.

창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다. 보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다. 하나의 토마토에 인접한 곳은 위, 아래, 왼쪽, 오른쪽, 앞, 뒤 여섯 방향에 있는 토마토를 의미한다. 대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다. 철수는 창고에 보관된 토마토들이 며칠이 지나면 다 익게 되는지 그 최소 일수를 알고 싶어 한다.

토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.

 

입력

첫 줄에는 상자의 크기를 나타내는 두 정수 M,N과 쌓아올려지는 상자의 수를 나타내는 H가 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M ≤ 100, 2 ≤ N ≤ 100, 1 ≤ H ≤ 100 이다. 둘째 줄부터는 가장 밑의 상자부터 가장 위의 상자까지에 저장된 토마토들의 정보가 주어진다. 즉, 둘째 줄부터 N개의 줄에는 하나의 상자에 담긴 토마토의 정보가 주어진다. 각 줄에는 상자 가로줄에 들어있는 토마토들의 상태가 M개의 정수로 주어진다. 정수 1은 익은 토마토, 정수 0 은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않은 칸을 나타낸다. 이러한 N개의 줄이 H번 반복하여 주어진다.

토마토가 하나 이상 있는 경우만 입력으로 주어진다.

 

출력

여러분은 토마토가 모두 익을 때까지 최소 며칠이 걸리는지를 계산해서 출력해야 한다. 만약, 저장될 때부터 모든 토마토가 익어있는 상태이면 0을 출력해야 하고, 토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다.

 

풀이 (BFS)

시간 복잡도 O(H*N*M) 공간 복잡도 O(H*N*M)

아이디어: 기존 (7576) 토마토 문제에서 3차원으로 공간을 잡고 퍼지도록 한다면 해결 할 수 있는 문제

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

public class Main {
    static class Pos{
        int x,y,h;
        Pos(int x,int y,int h){
            this.x = x;
            this.y = y;
            this.h = h;
        }
    }
    public static boolean judge(int[][][] box){
        for (int[][] ints : box) {
            for (int[] anInt : ints) {
                for (int i : anInt) {
                    if(i==0)
                        return false;
                }
            }
        }
        return true;
    }
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int M = Integer.parseInt(st.nextToken());
        int N = Integer.parseInt(st.nextToken());
        int H = Integer.parseInt(st.nextToken());
        Queue<ArrayList<Pos>> queue = new LinkedList<>();
        int[][][] map = new int[H][N][M];
        ArrayList<Pos> sp = new ArrayList<>();
        for (int k = 0; k < H; k++) {
            for(int i=0;i<N;i++){
                st = new StringTokenizer(br.readLine());
                for(int j=0;j<M;j++){
                    int n = Integer.parseInt(st.nextToken());
                    if(n==1) sp.add(new Pos(j,i,k));
                    map[k][i][j] = n;
                }
            }
        }

        queue.add(sp);

        int[][] ways = {{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}};

        int ans = -1;
        while(!queue.isEmpty()){
            ArrayList<Pos> cur = queue.poll();
            ArrayList<Pos> nextNode = new ArrayList<>();
            for (Pos pos : cur) {
                for (int[] way : ways) {
                    int y = pos.y + way[1];
                    int x = pos.x + way[0];
                    int h = pos.h + way[2];
                    if(x<0||x>=M||y<0||y>=N||h<0||h>=H||map[h][y][x]==-1||map[h][y][x]==1)
                        continue;
                    nextNode.add(new Pos(x,y,h));
                    map[h][y][x] = 1;
                }
            }
            if(!nextNode.isEmpty())
                queue.add(nextNode);
            ans++;
        }

        if(judge(map))
            System.out.println(ans);
        else
            System.out.println(-1);
    }
}

 

 

문제 링크

분류

너비 우선 탐색, 그래프 이론, 그래프 탐색

 

문제 설명

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일 때 걷는다면 1초 후에 X-1 또는 X+1로 이동하게 된다. 순간이동을 하는 경우에는 1초 후에 2*X의 위치로 이동하게 된다.

수빈이와 동생의 위치가 주어졌을 때, 수빈이가 동생을 찾을 수 있는 가장 빠른 시간이 몇 초 후인지 구하는 프로그램을 작성하시오.

 

입력

첫 번째 줄에 수빈이가 있는 위치 N과 동생이 있는 위치 K가 주어진다. N과 K는 정수이다.

 

출력

첫째 줄에 수빈이가 동생을 찾는 가장 빠른 시간을 출력한다.

둘째 줄에 어떻게 이동해야 하는지 공백으로 구분해 출력한다.

 

풀이 (BFS 너비 우선 탐색)

시간 복잡도 O(n) 공간 복잡도 O(n)

아이디어: 숨바꼭질과 동일 https://suhanlim.tistory.com/279

역추적은 따로 map 만큼의 history 배열을 만들어서 해당 칸에 방문하기전 이전 칸을 기록하는 방식을 이용하여 진행한다.

map[pos+1] = time+1, 과 같이 갱신 작업을 할 때 histroy[pos+1] = pos 와 같은 식으로 이전에 방문한 기록을 저장한다.

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

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        Queue<int[]> queue = new LinkedList<>();
        int[] map = new int[100001];
        int[] history = new int[100001];
        Arrays.fill(map,-1);
        ArrayList<Integer> ans = new ArrayList<>();
        int n = Integer.parseInt(st.nextToken());
        int k = Integer.parseInt(st.nextToken());

        map[n] = 0;
        queue.add(new int[]{n, 0});

        while(!queue.isEmpty()){
            int[] current = queue.poll();
            int pos = current[0];
            int time = current[1];
            // 목표를 잡은 경우
            if (pos == k) {
                System.out.println(time);
                break;
            }
			// 한 칸 뒤로
            if (pos - 1 >= 0 && map[pos - 1] == -1) {
                map[pos - 1] = time + 1;
                history[pos-1] = pos;
                queue.add(new int[]{pos - 1, time + 1});
            }
            // 한 칸 앞으로
            if (pos + 1 <= 100000 && map[pos + 1] == -1) {
                map[pos + 1] = time + 1;
                history[pos+1] = pos;
                queue.add(new int[]{pos + 1, time + 1});
            }
            // 텔레포트
            if (pos * 2 <= 100000 && map[pos * 2] == -1) {
                map[pos * 2] = time + 1;
                history[pos*2] = pos;
                queue.add(new int[]{pos * 2, time + 1});
            }
        }
        // 역추적 시작
        int search = k;
        Stack<Integer> stack = new Stack<>();
        while(search!=n){
            stack.push(search);
            search = history[search];
        }
        stack.add(n);
        while (!stack.isEmpty()) {
            System.out.print(stack.pop()+" ");
        }
    }
}

 

 

 

'PS > Gold' 카테고리의 다른 글

[Gold V] 숨바꼭질 3 - 13549 (Java)  (0) 2023.08.16
[Gold V] 토마토 - 7569 (Java)  (0) 2023.08.15
[Gold V] 토마토 - 7576 (Java)  (0) 2023.08.02
[Gold III] Happy Cow - 13002 (Java)  (0) 2023.07.29
[Gold III] 수확 - 1823 (Java)  (0) 2023.07.29

+ Recent posts