JAVA/알고리즘 서브 노트
[백준 15684 : JAVA] 사다리 조작
Kamea
2022. 10. 30. 12:29
https://www.acmicpc.net/problem/15684
[1회차] 10월 29일 ~ 10월 30일 : 거드름의 결과랄까...^^
문제 접근 방법 (사용 자료구조, 알고리즘)
이 문제는 시행착오가 많았어서 차례로 한번 보여줄까 한다
(1) 자료구조 (List + HashMap) + 조합
입력
(1) {N, M, H}
(2) 연결할 수 있는 사다리의 후보군들을 저장해 놓는 candidates(Point 리스트)에 (N) * (H + 1)개의 사다리를 넣어 놓는다
(3) {M개의 줄에는 연결되어 있는 사다리의 정보}를 입력 받으며 후보군에서 (사다리 + 해당 사다리와 연속된 위치에 있는 사다리) 를 빼준다 (remove) + info(HashMap<String, Integer>)에 key값으로는 위치, value는 1 또는 -1을 넣어준다.
→ 현재 사다리 정보가 (a, b)라면 (a, b) - (a,b+1) 이렇게 연결하므로 a+","+b 에는 1을, a+","+(b+1)에는 -1을 넣어준다
→ 사다리 이동 위치
로직
(1) M = 0 인 경우에는 진행하지 않고 0을 출력한다.
(2) candidates 에서 차례로 0, 1, 2, 3개의 사다리를 뽑는 경우의 수를 조합한다. → DFS
(3) 수를 다 조합했을 경우, 사다리를 탄다
(4) Point 객체에 (현재 탈 줄의 숫자(x), 현재 높이(y, 1)) 를 넣어주고 높이가 H까지 갈때 까지 이동을 한다.
(5) info에 해당 위치의 값이 있다면 해당 value 만큼 더해준다 → 1이면 현재 위치 기준으로 오른쪽, -1 이면 왼쪽으로 이동
(6) 만약 현재 탈 줄의 숫자와 맨 마지막 도착 위치가 맞지 않다면 더 이상 이동하지 않는다.
(7) 사다리를 타는 메서드에서 true를 반환할 경우 모든 재귀와 반복문을 종료하고 정답을 출력한다.
출력
3이상 또는 -1의 값에는 -1, 그 이외에는 찾은 정답을 출력한다.
결과 : 시간초과
대충 예상은 했는데 로직만 짜여진 느낌..?
코드
더보기
import java.awt.Point;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
public class Main {
static int N, M, H, selected[], ans;
static List<Point> candidates;
static Map<String, Integer> info;
static boolean visited[];
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(in.readLine());
// 1. 입력
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
H = Integer.parseInt(st.nextToken());
if(M == 0) {
System.out.println(0);
System.exit(0);
}
candidates = new ArrayList<>();
for (int i = 1; i < N; i++) {
for (int j = 1; j <= H; j++) {
candidates.add(new Point(j, i));
}
}
info = new HashMap<>();
for (int i = 0; i < M; i++) {
st = new StringTokenizer(in.readLine());
// (a, b) - (a, b+1)를 이어줌
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
// 사다리 있다고 체크 해주기
info.put(a+","+b, 1);
info.put(a+","+(b+1), -1);
// 본인 + 연속하는 사다리를 계산해서 후보군에서 빼주기
candidates.remove(new Point(a, b));
if(b - 1 >= 1) candidates.remove(new Point(a, b-1));
if(b + 1 <= N) candidates.remove(new Point(a, b+1));
}
// 2-1. 사다리 설치할 곳 뽑기 1 또는 2
for (int i = 1; i <= 3 && ans == 0; i++) {
selected = new int[i];
visited = new boolean[candidates.size()];
combi(i, 0, 0);
}
System.out.println(ans == 0? -1: ans);
}
private static void combi(int D, int depth, int start) {
if(depth == D) {
// 사다리 추가하기
for (int i = 0; i < D; i++) {
Point ladder = candidates.get(selected[i]);
info.put(ladder.x+","+ladder.y, 1);
info.put(ladder.x+","+(ladder.y+1), -1);
}
// 사다리 타기
if(game()) {
ans = D;
return;
}
for (int i = 0; i < D; i++) {
Point ladder = candidates.get(selected[i]);
info.put(ladder.x+","+ladder.y, 0);
info.put(ladder.x+","+(ladder.y+1), 0);
}
return;
}
for (int i = start, size = candidates.size(); i < size && ans == 0; i++) {
if(!visited[i]) {
visited[i] = true;
selected[depth] = i;
combi(D, depth+1, i+1);
visited[i] = false;
}
}
}
private static boolean game() {
for (int i = 1; i <= N; i++) {
Point pos = new Point(i, 1);
while(pos.y <= H) {
// 사다리랑 만나는지 확인 -> 만난다면 pos[0] 이동시켜주기
if(info.get(pos.y+","+pos.x) != null && info.get(pos.y+","+pos.x) != 0) {
pos.x += Integer.valueOf(info.get(pos.y+","+pos.x));
}
pos.y += 1;
}
if(pos.x != i) return false;
}
return true;
}
}
(2) 자료구조 (List + boolean[][]) + 조합
입력
(1) {N, M, H}
(2) 연결할 수 있는 사다리의 후보군들을 저장해 놓는 candidates(Point 리스트)에 (N) * (H + 1)개의 사다리를 넣어 놓는다
(3) {M개의 줄에는 연결되어 있는 사다리의 정보}를 입력 받으며 후보군에서 (사다리 + 해당 사다리와 연속된 위치에 있는 사다리) 를 빼준다 (remove) + ladderCheck(boolean[H+1][N+1])를 true 로 설정해준다.
로직
(1) M = 0 인 경우에는 진행하지 않고 0을 출력한다.
(2) candidates 에서 차례로 0, 1, 2, 3개의 사다리를 뽑는 경우의 수를 조합한다. → DFS
(3) 수를 다 조합했을 경우, ladderCheck를 복사한 배열(copy)에 연결할 사다리 정보를 true로 설정한 후 사다리를 탄다.
(4) Point 객체에 (현재 탈 줄의 숫자(x), 현재 높이(y, 1)) 를 넣어주고 높이가 H까지 갈때 까지 이동을 한다.
(5) copy[y][x] = true면 x에 1을 더해주고 그렇지 않다면 copy[y][x-1] = true이면 1을 빼준다.
(6) 만약 현재 탈 줄의 숫자와 맨 마지막 도착 위치가 맞지 않다면 더 이상 이동하지 않는다.
(7) 사다리를 타는 메서드에서 true를 반환할 경우 모든 재귀와 반복문을 종료하고 정답을 출력한다.
출력
3이상 또는 -1의 값에는 -1, 그 이외에는 찾은 정답을 출력한다.
메모리 & 시간
어마무시한 메모리와 시간, 심지어 컴파일러에 따라 시간 차이가 많이 나는데 이유는 모르겠다.
뭔가 수정할 필요를 느끼고 다시 한번의 수정에 돌입했다.
코드
더보기
import java.awt.Point;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
public class Main {
static int N, M, H, selected[], ans;
static List<Point> candidates;
static boolean visited[], ladderCheck[][], copy[][];
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(in.readLine());
// 1. 입력
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
H = Integer.parseInt(st.nextToken());
if(M == 0) {
System.out.println(0);
System.exit(0);
}
candidates = new ArrayList<>();
for (int i = 1; i < N; i++) {
for (int j = 1; j <= H; j++) {
candidates.add(new Point(j, i));
}
}
ladderCheck = new boolean[H + 1][N + 1];
for (int i = 0; i < M; i++) {
st = new StringTokenizer(in.readLine());
// (a, b) - (a, b+1)를 이어줌
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
// 사다리 있다고 체크 해주기
ladderCheck[a][b] = true;
// 본인 + 연속하는 사다리를 계산해서 후보군에서 빼주기
candidates.remove(new Point(a, b));
if(b - 1 >= 1) candidates.remove(new Point(a, b-1));
if(b + 1 <= N) candidates.remove(new Point(a, b+1));
}
ans = 4;
// 2-1. 사다리 설치할 곳 뽑기 1 또는 2
for (int i = 0; i <= 3 && ans == 4; i++) {
selected = new int[i];
visited = new boolean[candidates.size()];
combi(i, 0, 0);
}
System.out.println(ans == 4? -1: ans);
}
private static void combi(int D, int depth, int start) {
if(depth == D) {
copy = new boolean[H + 1][N + 1];
for (int i = 0; i <= H; i++) {
copy[i] = ladderCheck[i].clone();
}
// 사다리 추가하기
for (int i = 0; i < D; i++) {
Point ladder = candidates.get(selected[i]);
copy[ladder.x][ladder.y] = true;
}
// 사다리 타기
if(game()) {
ans = D;
return;
}
return;
}
for (int i = start, size = candidates.size(); i < size && ans == 4; i++) {
if(!visited[i]) {
visited[i] = true;
selected[depth] = i;
combi(D, depth+1, i+1);
visited[i] = false;
}
}
}
private static boolean game() {
for (int i = 1; i <= N; i++) {
Point pos = new Point(i, 1);
while(pos.y <= H) {
if(copy[pos.y][pos.x]) {
pos.x += 1;
} else if(pos.x - 1 >= 1 && copy[pos.y][pos.x-1]) {
pos.x -= 1;
}
pos.y += 1;
}
if(pos.x != i) return false;
}
return true;
}
}
(3) 자료구조 (List + boolean[][]) + 순열
입력
(1) {N, M, H}
(2) 연결할 수 있는 사다리의 후보군들을 저장해 놓는 candidates(Point 리스트)에 (N) * (H + 1)개의 사다리를 넣어 놓는다
(3) {M개의 줄에는 연결되어 있는 사다리의 정보}를 입력 받으며 후보군에서 (사다리 + 해당 사다리와 연속된 위치에 있는 사다리) 를 빼준다 (remove) + ladderCheck(boolean[H+1][N+1])를 true 로 설정해준다.
로직
(1) M = 0 인 경우와 사다리를 설치하지 않고도 이미 조작된 경우에는 진행하지 않고 0을 출력한다.
(2) candidates 에서 차례로 1, 2, 3개의 사다리를 뽑는 경우의 수를 순열로 생성한다.
→ 이 때 하나의 사다리를 연결할 때마다 사다리 타기를 해서 만약 사다리를 제대로 조작하지 못했으면 다음 재귀로 넘기고,
사다리를 제대로 조작했을 경우에는 재귀를 즉시 종료하고 답을 출력한다.
(4) Point 객체에 (현재 탈 줄의 숫자(x), 현재 높이(y, 1)) 를 넣어주고 높이가 H까지 갈때 까지 이동을 한다.
(5) copy[y][x] = true면 x에 1을 더해주고 그렇지 않다면 copy[y][x-1] = true이면 1을 빼준다.
(6) 만약 현재 탈 줄의 숫자와 맨 마지막 도착 위치가 맞지 않다면 false를 반환한다.
출력
3이상 또는 -1의 값에는 -1, 그 이외에는 찾은 정답을 출력한다.
메모리 & 시간
그 결과 아주 쬐끔 줄임
84ms 의 코드를 보았는데요 아예 후보군을 만들지 않고 하시더라구여...무슨 홀수 이렇게 해서.. 저는 아직 그 정도까지는 아닌 걸루...
코드
더보기
import java.awt.Point;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
public class Main {
static int N, M, H, ans;
static List<Point> candidates;
static boolean visited[], ladderCheck[][];
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(in.readLine());
// 1. 입력
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
H = Integer.parseInt(st.nextToken());
candidates = new ArrayList<>();
for (int i = 1; i < N; i++) {
for (int j = 1; j <= H; j++) {
candidates.add(new Point(j, i));
}
}
ladderCheck = new boolean[H + 1][N + 1];
for (int i = 0; i < M; i++) {
st = new StringTokenizer(in.readLine());
// (a, b) - (a, b+1)를 이어줌
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
// 사다리 있다고 체크 해주기
ladderCheck[a][b] = true;
// 본인 + 연속하는 사다리를 계산해서 후보군에서 빼주기
candidates.remove(new Point(a, b));
if(b - 1 >= 1) candidates.remove(new Point(a, b-1));
if(b + 1 <= N) candidates.remove(new Point(a, b+1));
}
if(M == 0|| game(ladderCheck)) {
System.out.println(0);
System.exit(0);
}
ans = 4;
for (int i = 1; i <= 3 && ans == 4; i++) {
visited = new boolean[candidates.size()];
combi(i, 0, ladderCheck);
}
System.out.println(ans == 4? -1: ans);
}
private static void combi(int D, int depth, boolean[][] laddercopy) {
if(depth == D) {
return;
}
for (int i = 0, size = candidates.size(); i < size && ans == 4; i++) {
if(!visited[i]) {
visited[i] = true;
laddercopy[candidates.get(i).x][candidates.get(i).y] = true;
if(!game(laddercopy)) {
combi(D, depth+1, laddercopy);
visited[i] = false;
laddercopy[candidates.get(i).x][candidates.get(i).y] = false;
} else {
ans = D;
break;
}
}
}
}
private static boolean game(boolean[][] copy) {
for (int i = 1; i <= N; i++) {
Point pos = new Point(i, 1);
while(pos.y <= H) {
if(copy[pos.y][pos.x]) {
pos.x += 1;
} else if(pos.x - 1 >= 1 && copy[pos.y][pos.x-1]) {
pos.x -= 1;
}
pos.y += 1;
}
if(pos.x != i) return false;
}
return true;
}
}
문제를 푼 시간 (문제 해석 + 문제 접근 + 코드 구현)
문제 해석 | 문제 접근 | 코드 구현 |
15분 | 20분 | 하루가 넘어감...잠 잔 시간까지 포함하면(13시간), 아니면 (2시간 30분) |