[Baekjoon] ACM Craft

Date:     Last Updated:

ACM Craft

출처: Baekjoon
ACM Craft

문제의 이해

  • 처음 문제를 보고 “이 문제는 어떤 알고리즘이나 방법으로 풀어야 하겠다” 라고 생각되어서 문제 페이지 아래 알고리즘 분류를 봤다.
  • 다이나믹 프로그래밍, 그래프 이론, 위상정렬 이렇게 3가지가 써있었다.
  • 다만 일단은 무식하지만 BF로 풀어보기로 했다. 아마도 시간초과가 날 것이라고 예상했고, 예상대로 였다.
  • DP는 문제 내에서 구현하면 되겠지 생각하고 그래프 이론과 위상정렬을 검색해서 이해를 해봤다.
  • 처음에는 이해가 잘 가지는 않았지만 보다보니 위상정렬을 사용하면 BF로 푼 것에 비해서 비약적으로 속도를 상승시킬 수 있을 것 같았다.

내가 생각한 풀이 방법

  • 엄청나게 어려운 문제는 아니고 알고리즘 공부를 하면서 적용시켜 나가면 충분히 풀 수 있는 문제라고 생각했다.
  • 물론 결과적으로 거의 3일을 고민하고, 이것저것 방법을 고민하고, 바꿔가면서 겨우겨우 풀었다.
  • 다른 무엇보다 언제나 이런 알고리즘 코딩의 결론은, 막히면 생각하고 생각하고 생각하다가 더 생각이 안나면 방법을 바꿔서 초기화하고 다시 푸는 것이 나을 수도 있다는 것이다.

풀이

첫 번째 풀이 : 시간초과. 실패

  • 일단 첫번째 BF로 풀어본 풀이 방법이다.
  • BF이기도 하면서 DFS 방식이기도 하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.algorithm;

import java.util.ArrayList;
import java.util.Scanner;

class Building{
	int buildTime = 0;
	ArrayList<Building> requireBuiling = null;
	public Building(int pTime){
		buildTime = pTime;	//건설시간
		requireBuiling = new ArrayList<>(); //현재 건물을 짓기 위해 필요한 건물LIST
	}
	public void addBuilding(Building pBuilding) {	//조건 건물 추가
		requireBuiling.add(pBuilding);
	}
	public int recursiveBuilding(){
		if(requireBuiling.size() == 0) {	//재귀 탈출 조건(건물을 짓기위한 조건이 없는 건물을 만났을 때)
			return buildTime;
		}

		int max = 0;	//현재 건물을 짓기 위한 조건 건물들 중에서 제일 건설시간이 긴 것을 알아낼 변수
		int returnValue = 0;
		for (Building building : requireBuiling) {
			returnValue = building.recursiveBuilding();	//재귀 탈출 조건을 만날때까지 재귀
			max = max < returnValue ? returnValue : max;	//다른 값이 더 크다면 max값 변경
		}
		return buildTime + max;	//건설 시간값 누적
	}
}


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

		ArrayList<Building>[] testCase = null;	//테스트케이스 LIST배열

		int testCaseNumber = sc.nextInt();	//테스트 케이스 갯수 입력

		int buildingNumber = 0;
		int ruleNumber = 0;
		int[] target = null;


		testCase = new ArrayList[testCaseNumber];
		target = new int[testCaseNumber];

		//입력된 테스트 케이스 횟수만큼 반복.
		for (int i = 0; i < testCaseNumber; i++) {
			testCase[i] = new ArrayList<>();	
			buildingNumber = sc.nextInt();	//건물의 개수
			ruleNumber = sc.nextInt();		//제한 조건의 개수

			//건물의 건설시간 입력
			for (int j = 0; j < buildingNumber; j++) {
				testCase[i].add(new Building(sc.nextInt()));
			}

			//제한 조건의 수만큼 입력
			for (int j = 0; j < ruleNumber; j++) {
				int requireBuildingNumber = sc.nextInt();	
				int ownNumber = sc.nextInt();

				//				requireBuildingNumber이 ownNumber건물을 짓기위한 필요 조건이다.


				//testCase[i].get(sc.nextInt() - 1).addBuilding(testCase[i].get(sc.nextInt() - 1));

				//배열은 0부터 시작하므로 -1
				//조건이 2 4 라면 ownNumber가 4이고 requireBuildingNumber가 2이다.
				//
				testCase[i].get(ownNumber - 1).addBuilding(testCase[i].get(requireBuildingNumber - 1));
			}

			//지어야하는 특정 건물 W
			target[i] = sc.nextInt();
		}

		//테스트 케이스만큼 출력
		for (int i = 0; i < testCaseNumber; i++) {
			System.out.println(testCase[i].get(target[i] - 1).recursiveBuilding());
		}
	}
}

클래스를 자주 사용해보려고 일부러 빌딩이라는 클래스를 만들어서 풀었다.

두 번째 풀이 : 틀렸습니다. 실패

  • 알고리즘 분류에 나와있던 위상정렬을 사용 해보았다. 58%에서 계속해서 실패해서 뭐가 문제인지 확인하려고 다른 사람들 질문을 확인했다.
  • 다른 사람들은 위상정렬로 풀었다고 하는데, 난 결국 고민고민하다가 시작지점이 여러개거나 하나에서 출발해서 여러개로 나눠지는 것을 체크하지 못해서 실패했다.
  • 어떤 문제에서 어떻게 풀면 되겠다고 떠오르면 그 방식대로 풀면 되는데 그게 안떠오를 때가 있다(많다…)
  • 기본 예제는 모두 통과 했는데 질문에 나오는 반례들을 결국 통과하지 못했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//1						
//6 6
//10 5 1 1 9 8
//1 2
//1 4
//2 3
//4 5
//3 6
//5 6
//6
//정답 28
//내 코드 32


//1
//10 5
//1 2 3 4 5 6 7 8 9 10
//1 6
//2 7
//3 8
//4 9
//5 10
//6
//정답 7
//내 코드 11
  • 아래쪽 반례는 조건을 주면 풀 수 있을 것 같은데 위 반례는 아무리 생각해도 방법이 떠오르지 않았다.
  • 그래서 이 풀이는 남겨두고 처음에 풀었던 DFS에 DP를 사용해서 풀어보자고 생각했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package com.algorithm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Scanner;

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

		//테스트케이스 수 
		//건물의 수, 조건의 수
		//건물의 건설 시간(건물의 수 만큼)
		//건물의 조건(조건의 수 만큼)

		int testCase = sc.nextInt();	//테스트 케이스

		int bCount = 0;
		int bRequireCount = 0;
		HashMap<Integer, Integer> bTime = new HashMap<>();
		HashMap<Integer, ArrayList<Integer>> bAdjust = new HashMap<>();
		ArrayList<Integer> queue = new ArrayList<>();
		//		HashMap<Integer, Integer> bDegree = new HashMap<>();
		int[] bDegree = null;
		int target = 0;

		int[] sum = new int[testCase];
		
    //테스트케이스 수 만큼 반복 실행
		for (int p = 0; p < testCase; p++) {
			bTime.clear();
			bAdjust.clear();
			queue.clear();
      //모든 저장소 클리어

			bCount = sc.nextInt();  //건물의 수 입력
			bRequireCount = sc.nextInt(); //건물의 조건 갯수 입력
			bDegree = new int[bCount + 1];  //건물의 차수를 저장할 배열(배열이고 0번째를 사용하지 않으려고 +1 해줌)

			//건설 시간 입력
			for (int i = 1; i <= bCount; i++) {
		    int inputTime = sc.nextInt();
				bTime.put(i, inputTime);
				bDegree[i] = 0;
				bAdjust.put(i, new ArrayList<Integer>());
			}

			//배열로 했으니 0부터 시작
			//모든 번호는 -1씩 ㅎ
			for (int i = 1; i <= bRequireCount; i++)
			{
				Integer lBuild = sc.nextInt();
				Integer rBuild = sc.nextInt();

				bAdjust.get(lBuild).add(rBuild);

				bDegree[rBuild]++;
			}
			target = sc.nextInt();

			while(true) {
				for (int i = 1; i <= bCount; i++) {
					if (bDegree[i] == 0) {
						queue.add(i);	//여기 저장되는 i는 차수가 0인 정점
					}
				}

				if(queue.size() == 0) {
					break;
				}

				int max = - 1;
				int bNumber = 0;
				for (int i : queue) {	//차수가 0이 된 모든 정점을 돌면서 연결된 선을 없애면 된다.

					//queue에 있는 값이 target(4)이라면.
					//이 부분이 왜 필요하냐면 차수가 같고, 건설 시간이 같을때 같은 차수에서의 순서가
					//1,4 일때 아래에서 비교하면 1이 순서상 먼저이기 때문에 따로 처리를 해줘야 한다.

					if(i == target) {
						bNumber = i;
						bDegree[i] = -1;
						for (int j = 0; j < bCount + 1; j++) {
							bDegree[j] = -1;
						}
						break;
					}

					if (max < bTime.get(i)) {
						max = bTime.get(i);
						bNumber = i;
					}
					int arraySize = bAdjust.get(i).size();

					for (int j = 0; j < arraySize; j++) {
						int deleteLine = bAdjust.get(i).get(j);
						bDegree[deleteLine]--;
					}
					bDegree[i] = -1;
				}
				sum[p] += bTime.get(bNumber);
				queue.clear();
			}
		}
		for (int i = 0; i < testCase; i++) {
			System.out.println(sum[i]);
		}

	}
}

세 번째 풀이 : 성공

  • 첫번째 코드에서 주석을 달아놓은 2군데만 추가 시켰다.
  • 알고리즘 문제라서 딱히 상속같은 거 안쓰는데 eclipse에서 같은 패키지에 같은 이름이 있어서 그런지 중복이름이 안되어서 그냥 상속시켰다.
  • 첫번째 코드와 달라진 부분이 채 5줄이 안되는데 이렇게 성능이 차이가 난다니.. 진짜 알고리즘은 알다가도 모르겠다.
  • 특히 DP는 썼던 것인데도 어떻게 적용해야하지라는 고민이 머리속에서 잠시 있었어서 좀 더 몸에 체득을 시켜야겠다고 생각이 들었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.algorithm;

import java.util.ArrayList;
import java.util.Scanner;

class Building_valueSave extends Building{
	
	int keepValue = -1;	//DFS 를 했을 때 이미 검사한 부분은 다시 검사하지 않도록 값을 저장해두는 부분
	
	public Building_valueSave(int pTime) {
		super(pTime);
	}

	
	public int recursiveBuilding(){
		if(keepValue != -1) {	// -1이라면 아직 이 루트를 search하지 않은 것이고 값이 있다면 서치 했으므로 
			return keepValue;
		}
		if(requireBuiling.size() == 0) {	
			return buildTime;
		}

		int max = 0;	
		int returnValue = 0;
		for (Building building : requireBuiling) {
			returnValue = building.recursiveBuilding();	
			max = max < returnValue ? returnValue : max;
		}
		
		keepValue = buildTime + max;
		return buildTime + max;
	}
}



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

		ArrayList<Building_valueSave>[] testCase = null;	

		int testCaseNumber = sc.nextInt();

		int buildingNumber = 0;
		int ruleNumber = 0;
		int[] target = null;

		testCase = new ArrayList[testCaseNumber];
		target = new int[testCaseNumber];

		for (int i = 0; i < testCaseNumber; i++) {
			testCase[i] = new ArrayList<>();	
			buildingNumber = sc.nextInt();
			ruleNumber = sc.nextInt();		

			for (int j = 0; j < buildingNumber; j++) {
				testCase[i].add(new Building_valueSave(sc.nextInt()));
			}

			for (int j = 0; j < ruleNumber; j++) {
				int requireBuildingNumber = sc.nextInt();	
				int ownNumber = sc.nextInt();
				testCase[i].get(ownNumber - 1).addBuilding(testCase[i].get(requireBuildingNumber - 1));
			}

			target[i] = sc.nextInt();
		}

		for (int i = 0; i < testCaseNumber; i++) {
			System.out.println(testCase[i].get(target[i] - 1).recursiveBuilding());
		}
	}
}

마무리

  • 진짜 첫 번째 풀이 코드는 금방 짰는데 두 번째 풀이에서 거의 2일 잡아먹은 느낌이다. 처음에는 RunTime Error가 미친듯이 떠서 그거 수정한다고 같은 코드를 다른 방식으로 짰고
  • 그 다음에는 58%에서 계속해서 틀려서 정말 너무 고생했다.
  • 솔직히 두 번째 풀이에서 포기하고 싶은 마음이 진짜 컸는데 코드 껐다가 다시 킨게 몇번이다.
  • 다음에 좀 더 빨리 해보자.


Leave a comment