정답률 29퍼센트의 그리디 문제이다.
매우 어려운 문제인 것 같고 어려워서 금방 까먹을 것 같기 때문에 백준님의 강의를 통해 정리하려 한다.
r행 c열의 표 모양에서 가장 왼쪽 위 칸 부터 가장 오른쪽 아래 칸 까지
방문하는 점수의 합이 최대로 가는 경로를 출력하면 된다.
방문하지 않은 칸이 있어도 된다.
물론 전부 다 방문하는게 최고로 좋다.
이런 식으로 전부 다 방문하거나
이런 식으로 전부 다 방문할 수 있다.
하지만 왼쪽 가장 위부터 오른쪽 가장 아래까지 전부 다 방문할 수 없는 경우가 존재한다.
행과 열의 길이가 둘 다 짝수 인경우에는 절대 전부 다 방문할 수가 없다.
행과 열 둘 중 하나라도 홀수라면 전부 다 방문 할 수 있다.
위의 행렬을 체스판이라고 생각하면
행과 열이 모두 짝수인 경우엔
시작점과 도착점이 모두 흰색이다.
(행과 열중 하나라도 홀수인 경우엔 시작점 흰색, 도착점 검은색형태를 띈다.)
흰->검->흰->검->흰 이런 식으로 (흰3 검2)
흰색으로 시작해서 흰색으로 끝나야 하기 때문에
흰색의 수가 검정색의 수보다 하나 많아야
모든 칸을 방문할 수 있다.
하지만 행과 열이 짝수인 경우엔
흰색과 검정색의 수가 같기 때문에
흰색의 수를 하나 늘리거나 검정색의 수를 하나 없애야만 모든 칸을 방문할 수 있다.
이 문제는 행렬이 주어지기 때문에 흰색의 수를 하나 늘리는것은 불가능 하므로
검정색 칸을 하나 방문하지 않는 것으로 흰색의 수를 검정색의 수보다 하나 많게 할 수 있다.
따라서 가장 점수가 낮은 검정칸 하나를 빼고 방문하면 가장 큰 점수를 만들어 낼 수 있다.
하지만 문제는 점수를 출력하는 것이 아니라 경로를 출력하는 것이다.
행과 열중 하나라도 홀수 인 경우엔
이런식으로 모든 칸을 방문한 경로를 출력하면 되고
행과 열 둘다 짝수인 경우엔
문제를 살짝 변형해서 풀어야 한다.
시작점과 도착점에서 각자 출발해서 서로 만날 때까지 이동하는 문제로.
검정 칸중 가장 낮은 점수를 검은 칸으로 표시해 두고
시작점과 도착점의 인접한 행 2줄을 비교하여 검은칸이 없다면
그 경로를 StringBuilder에 우선 저장한다.
그 다음 시작점 A는 인접한 행 2줄에서 검은 칸이 있기 때문에 가만히 있고
B는 인접한 행 2줄에 아무것도 없기 때문에 또 그 경로를 스트링빌더에 저장한다.
이런식으로 가다보면
행의 크기는 언젠가 2가 되게 된다.
그러면 똑같은 방식으로 이번에는 열을 2칸씩 비교해서 그 경로를 저장한다.
그렇게 쭉쭉 해나가다 보면 언젠간 무조건 두 가지의 경우로 남게 된다.
왼쪽 경우 오른쪽 경우
그림처럼 경로를 다시 빌더에 저장하면 된다.
시작점과 도착점에서 따로 출발하기 때문에
StringBuilder를 2개 만들어서
따로 저장하고 도착점에서 출발하는 경우엔
다 저장하고 역순으로 시작점 빌더에 이어주서 출력해주면 된다.
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
|
public class Q2873 {
public static void append(StringBuilder s, char c, int cnt) { //cnt만큼 스트링빌더에 char c를 삽입하는 메소드
for (int i=0; i<cnt; i++) {
s.append(c);
}
}
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int[][] a = new int[n][m];
for (int i=0; i<n; i++) {
for (int j=0; j<m; j++) {
a[i][j] = sc.nextInt();
}
}
StringBuilder s = new StringBuilder();
if (n%2 == 1) { //n이 홀수인경우
for (int i=0; i<n; i++) {
if (i%2 == 0) {
append(s, 'R', m-1);
if (i != n-1) {
append(s, 'D', 1);
}
}
else {
append(s, 'L', m-1);
append(s, 'D', 1);
}
}
}
else if (m%2 == 1) { //m이 홀수인 경우
for (int j=0; j<m; j++) {
if (j%2 == 0) {
append(s, 'D', n-1);
if (j != m-1) {
append(s, 'R', 1);
}
} else {
append(s, 'U', n-1);
append(s, 'R', 1);
}
}
}
else { //n과m이 둘다 짝수인 경우
int x, y;
x = 0;
y = 1;
for (int i=0; i<n; i++) { //검정칸 중 젤 작은 수를 찾는다.
for (int j=0; j<m; j++) {
if ((i+j)%2 == 1) {
if (a[x][y] > a[i][j]) {
x = i;
y = j;
}
}
}
}
int x1 = 0;
int y1 = 0;
int x2 = n-1;
int y2 = m-1;
StringBuilder s2 = new StringBuilder();
while (x2 - x1 > 1) { //시작점과 도착점에서 인접한 2줄 검정칸이 없다면 지우기
if (x1/2 < x/2) {
append(s, 'R', m-1);
append(s, 'D', 1);
append(s, 'L', m-1);
append(s, 'D', 1);
x1 += 2;
}
if (x/2 < x2/2) {
append(s2, 'R', m-1);
append(s2, 'D', 1);
append(s2, 'L', m-1);
append(s2, 'D', 1);
x2 -= 2;
}
}
while (y2 - y1 > 1) { //2줄 남았을 경우 인접한 2칸에서 검정칸이 없다면 지우기
if (y1/2 < y/2) {
append(s, 'D', 1);
append(s, 'R', 1);
append(s, 'U', 1);
append(s, 'R', 1);
y1 += 2;
}
if (y/2 < y2/2) {
append(s2, 'D', 1);
append(s2, 'R', 1);
append(s2, 'U', 1);
append(s2, 'R', 1);
y2 -= 2;
}
}
if (y == y1) { //2x2칸 남았을 경우
append(s, 'R', 1);
append(s, 'D', 1);
} else {
append(s, 'D', 1);
append(s, 'R', 1);
}
s2.reverse(); //도착점에서 출발한 경로 뒤집기
s.append(s2);
}
System.out.println(s);
}
}
|
'Algorithm' 카테고리의 다른 글
백준 2263 트리의 순회 Java (0) | 2019.05.20 |
---|---|
백준 11729 하노이 탑 이동 순서 Java (0) | 2019.05.17 |
백준 1107 리모컨 Java (0) | 2019.05.14 |
백준 1201 NMK Java (0) | 2019.05.13 |
백준 11052 카드 구매하기 Java (0) | 2019.05.12 |