https://www.acmicpc.net/problem/17144

 

17144번: 미세먼지 안녕!

미세먼지를 제거하기 위해 구사과는 공기청정기를 설치하려고 한다. 공기청정기의 성능을 테스트하기 위해 구사과는 집을 크기가 R×C인 격자판으로 나타냈고, 1×1 크기의 칸으로 나눴다. 구사과는 뛰어난 코딩 실력을 이용해 각 칸 (r, c)에 있는 미세먼지의 양을 실시간으로 모니터링하는 시스템을 개발했다. (r, c)는 r행 c열을 의미한다. 공기청정기는 항상 왼쪽 열에 설치되어 있고, 크기는 두 행을 차지한다. 공기청정기가 설치되어 있지 않은 칸에는 미세먼

www.acmicpc.net

처음에 큐를 사용하지 않고 벡터로 구현을 했어서 헤맸던 문제였다...

공기청정기 바람 나오는 거 구현할 때에도 범위 때문에 계속 헷갈렸었다

공기청정기는 위쪽과 아래쪽을 나눠서 각각 구현해줬다. 그냥 문제에서 시키는 대로 한 칸씩 이동시켜주면 된다.

 

미세먼지 확산의 경우에는 확산되는 먼지는 따로 저장해서 나중에 한번에 더해줘야 한다.

바로 더해주면 확산된 곳에서 같은 시간에 다시 확산될 수 도 있기 때문이다

이 부분이랑 공기청정기에서 바람 나와서 이동할 때 범위만 조심해주면 될 것 같다!

 

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
 
int R, C, T;
int map[50][50];
int sum[50][50];
int air[2];
 
int dx[] = { 0,-1,0,1 };
int dy[] = { 1,0,-1,0 };
 
void goUpair() {
    int x, y;
    x = air[0- 1;
    y = 0;
 
    if (x < 0return;
 
    while (x > 0) {
        map[x][y] = map[x - 1][y];
        x--;
    }
    while (y < C - 1) {
        map[x][y] = map[x][y + 1];
        y++;
    }
    while (x < air[0]) {
        map[x][y] = map[x + 1][y];
        x++;
    }
    while (y > 1) {
        map[x][y] = map[x][y - 1];
        y--;
    }
    map[x][1= 0;
 
}
 
void goDownair() {
    int x, y;
    x = air[1+ 1;
    y = 0;
 
    if (x >= R) return;
 
    while (x < R - 1) {
        map[x][y] = map[x + 1][y];
        x++;
    }
    while (y < C - 1) {
        map[x][y] = map[x][y + 1];
        y++;
    }
    while (x > air[1]) {
        map[x][y] = map[x - 1][y];
        x--;
    }
    while (y > 1) {
        map[x][y] = map[x][y - 1];
        y--;
    }
    map[x][1= 0;
}
 
void spread() {
    queue<pair<intint>> q;
 
    //먼지가 있는 곳을 모두 큐에 넣어준다.
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < C; j++) {
            if (map[i][j] > 0) {
                q.push(make_pair(i, j));
            }
        }
    }
 
 
    int x, y;
    while (!q.empty()) {
        x = q.front().first;
        y = q.front().second;
        q.pop();
        int amt = map[x][y] / 5;
 
        for (int k = 0; k < 4; k++) {
            int nx = x + dx[k];
            int ny = y + dy[k];
            if (nx < 0 || ny < 0 || nx >= R || ny >= C) continue;
            if (map[nx][ny] == -1continue;
 
            //확산될때 원래자리에서는 빼주고
            //확산될 자리에서는 더해준다.
            map[x][y] -= amt;
            sum[nx][ny] += amt;
        }
    }
 
    //확산된 먼지들을 더해준다.
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < C; j++) {
            map[i][j] += sum[i][j];
        }
    }
 
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
 
    cin >> R >> C >> T;
 
    bool flag = true;
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < C; j++) {
            cin >> map[i][j];
            if (map[i][j] == -1) {
                if (flag) {
                    //첫번째 공기청정기
                    air[0= i;
                    flag = false;
                }
                else {
                    //두번째 공기청정기
                    air[1= i;
                }
            }
        }
    }
 
 
    //T초 동안 확산과 공기청정(위쪽, 아래쪽)을 반복
    for (int i = 0; i < T; i++) {
        memset(sum, 0sizeof(sum));
        spread();
        goUpair();
        goDownair();
    }
 
 
    //남아있는 먼지 수를 계산
    int ans = 0;
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < C; j++) {
            ans += map[i][j];
        }
    }
 
 
    //공기청정기 2개때문에 빠진 값을 다시 더해준다.
    cout << ans + 2 << '\n';
 
    return 0;
}
Colored by Color Scripter
 

'BOJ' 카테고리의 다른 글

[BOJ] 17135. 캐슬 디펜스  (0) 2019.08.01
[BOJ] 16236. 아기 상어  (0) 2019.07.31
[BOJ] 17140. 이차원 배열과 연산  (0) 2019.07.30
[BOJ] 17142. 연구소 3  (0) 2019.07.30
[BOJ] 1806. 부분합  (0) 2019.07.16

https://www.acmicpc.net/problem/17140

 

17140번: 이차원 배열과 연산

첫째 줄에 r, c, k가 주어진다. (1 ≤ r, c, k ≤ 100) 둘째 줄부터 3개의 줄에 배열 A에 들어있는 수가 주어진다. 배열 A에 들어있는 수는 100보다 작거나 같은 자연수이다.

www.acmicpc.net

초기 배열의 크기가 3X3이고 1초가 지날 때마다 배열에 연산이 적용되는데

최종적으로 배열의 r행c열에 들어있는 값이 k가 되는 시간을 찾는 문제이다.

시간이 100을 넘어가는 경우에는 -1을 출력한다.

 

R연산과 C연산은 다음과 같다.

  • R 연산: 배열 A의 모든 행에 대해서 정렬을 수행한다. 행의 개수 ≥ 열의 개수인 경우에 적용된다.
  • C 연산: 배열 A의 모든 열에 대해서 정렬을 수행한다. 행의 개수 < 열의 개수인 경우에 적용된다.

 

이 문제에서 중요한 부분은 각 숫자의 등장 횟수를 센다음에 어떻게 새로 저장해 주느냐인 것 같다.

나는 벡터를 이용해서 저장했다. 벡터의 자료 형을 pair로 넣고 정렬할 경우

pair 중 앞부분 우선으로 먼저 정렬되고 그다음에 두 번째 걸로 정렬되는 점을 이용했다.

 

이 문제에서는 먼저 수의 등장 횟수순으로 정렬되어야 하기 때문에 등장 횟수를 앞에 넣어주고 수를

뒤에 넣어주었다. 그리고 다시 배열에 저장할때는 벡터의 뒤에 거를 먼저 빼서 넣어주고 앞에 거를 넣어줬다.

 

그리고 또 중요한 것은 바뀌는 행의 크기와 열의 크기이다.

매번 연산때마다 크기는 줄어들거나 커질 수 있는데 해당 연산에서의 최댓값으로 다시 갱신해주어야 한다.

 

 

자세한 설명은 코드의 R연산(rcalc) 부분을 참고!

C연산은 R연산을 열 기준으로 바꿔주면 되므로 생략했다.

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
 
int map[150][150];
int cnt[101];
int r, c, k;
int rsize, csize;
vector<pair<intint>> vt;
 
void c_calc() {
    int new_rsize = 0;
    for (int i = 0; i < csize; i++) {
 
        for (int j = 0; j < rsize; j++) {
            cnt[map[j][i]]++;
        }
 
        for (int k = 1; k <= 100; k++) {
            if (cnt[k] != 0) {
                vt.push_back(make_pair(cnt[k], k));
                cnt[k] = 0;
            }
        }
 
        sort(vt.begin(), vt.end());
 
 
        int vtsize = vt.size();
        int newsize = vtsize * 2;
        new_rsize = max(new_rsize, newsize);
        int index = 0;
        for (int k = 0; k < newsize; k += 2) {
            map[k][i] = vt.at(index).second;
            map[k + 1][i] = vt.at(index).first;
            index++;
        }
 
        for (int k = newsize; k < rsize; k++) {
            map[k][i] = 0;
        }
 
 
        vt.clear();
    }
 
    rsize = new_rsize;
}
 
 
void rcalc() {
 
    int new_csize = 0; //이번 연산에서 새로 바뀔 열 크기 중 최댓값이 저장된다.
 
    //모든 행에 대해서 R연산을 해준다.
    for (int i = 0; i < rsize; i++) {
 
        //숫자의 등장 횟수를 세기 위해서, 각 열의 숫자를 인덱스로 cnt배열의 값을 증가시킨다.
        for (int j = 0; j < csize; j++) {
            cnt[map[i][j]]++;
        }
 
 
        //개수가 0개가 아니라면 벡터에 넣어주고 cnt배열은 다시 0으로 초기화 해준다.
        for (int k = 1; k <= 100; k++) {
            if (cnt[k] != 0) {
 
                //수의 등장횟수로 먼저 정렬되므로 수의 등장횟수를 앞에 넣어주고
                //해당 수를 뒤에 넣어준다.
                vt.push_back(make_pair(cnt[k], k));
                cnt[k] = 0;
            }
        }
 
 
        //정렬
        sort(vt.begin(), vt.end());
 
 
        //쌍으로 벡터에 들어가있으므로 하나씩 꺼내주기 위해 2를 곱해준다.
        int vtsize = vt.size();
        int newsize = vtsize * 2;
 
        //현재 연산의 최대 열 길이(new_csize가 이번 연산이 끝나면 csize(열 사이즈)로 바뀐다)
        new_csize = max(new_csize, newsize);
 
 
        //map에 다시 넣어준다.
        int index = 0;
        for (int k = 0; k < newsize; k += 2) {
            //수를 뒤에 넣었으므로 두번째 값(second)을 먼저 꺼낸다.
            map[i][k] = vt.at(index).second;
 
            //수의 등장 횟수를 앞(first)에 넣어놨으므로 나중에 꺼낸다. 
            map[i][k + 1= vt.at(index).first;
            index++;
        }
 
        //숫자를 채우고 남은 칸을 0으로 채워준다. (열 크기까지)
        for (int k = newsize; k < csize; k++) {
            map[i][k] = 0;
        }
 
 
        //벡터 정리
        vt.clear();
    }
 
    //열 크기는 이번 연산에서 가장 긴 열의 크기로 바뀐다
    //(줄어들 수 도 있고 늘어날 수 도 있음)
    csize = new_csize;
 
}
 
 
void solve() {
    int time = 0;
    while (true) {
 
        //찾은 경우
        if (map[r][c] == k) {
            cout << time << '\n';
            break;
        }
 
        time++;
 
        //시간이 100을 넘어가는 경우
        if (time > 100) {
            cout << -1 << '\n';
            break;
        }
 
        //R연산
        if (rsize >= csize) {
            rcalc();
        }
        else {//C연산
            c_calc();
        }
    }
 
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
 
    cin >> r >> c >> k;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            cin >> map[i][j];
        }
    }
 
    //index가 0번부터 시작하므로 1씩 빼준다.
    r--;
    c--;
 
    rsize = 3;
    csize = 3;
 
    solve();
 
    return 0;
}
Colored by Color Scripter
 

 

'BOJ' 카테고리의 다른 글

[BOJ] 16236. 아기 상어  (0) 2019.07.31
[BOJ] 17144. 미세먼지 안녕!  (0) 2019.07.30
[BOJ] 17142. 연구소 3  (0) 2019.07.30
[BOJ] 1806. 부분합  (0) 2019.07.16
[BOJ] 2003. 수들의 합 2  (0) 2019.07.15

https://www.acmicpc.net/problem/17142

 

17142번: 연구소 3

인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 바이러스는 활성 상태와 비활성 상태가 있다. 가장 처음에 모든 바이러스는 비활성 상태이고, 활성 상태인 바이러스는 상하좌우로 인접한 모든 빈 칸으로 동시에 복제되며, 1초가 걸린다. 승원이는 연구소의 바이러스 M개를 활성 상태로 변경하려고 한다. 연구소는 크기가 N×N인 정사각형으로 나타낼 수 있으며, 정사각형은 1×1 크기의 정사각형으로 나누어져 있다. 연구소는

www.acmicpc.net

주어진 배열에 있는 바이러스들 중 m개를 골라 활성화시켜서 연구소에 퍼뜨리는 문제이다.

m개를 고르는 모든 경우를 해보고, 골랐을 때마다 bfs로 바이러스를 퍼뜨려서

모두 퍼뜨리는데 걸리는 최소시간을 구하면 된다. 즉 완전 탐색 + bfs 문제이다.

 

이 문제에서 주의 할점은 처음에 활성화하지 않은 바이러스들이다.

이 바이러스들은 애초에 퍼져있는 상태나 마찬가지이므로 이 위치는 0초에 퍼져있는 상태나 마찬가지이다.

그래서 처음에 시간을 0으로 처리해줬다가 bfs부분에서 넘어가 버려서 다음과 같은 테스트 케이스에서 -1이 나왔다.

(원래 정답은 2가 나와야 함)

 

4 2

0110

2112

2112

0110

 

활성화하지 않은 바이러스를 벽처럼 여겨서 넘어가지 못했기 때문이다.

그래서 일단 얘네들도 빈칸처럼 똑같이 처리해줬다.

그리고 마지막에 모두 퍼뜨리는데 걸리는 시간을 구할 때 좌표의 값이 2라면(바이러스였다면) 최대 시간으로 간주하지 않는다.

밑의 코드에서는 0인 경우(원래 빈칸이었던 경우)에만 최대 시간을 계산해주었다.

 

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
 
int n, m;
int map[50][50];
int time[50][50];
int ans;
vector<pair<intint>> virus;
vector<pair<intint>> alive;
 
int dx[] = { 0,0,1,-1 };
int dy[] = { 1,-1,0,0 };
 
int check() {
    int maxtime = 0;
 
    //모든 칸을 검사
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
 
            //처음에 활성화하지 않은 바이러스의 시간을 최대값으로 저장하면 안되므로
            //현재 칸이 원래 빈칸이었고(바이러스가 아니고)
            // 최대시간보다 더 긴 시간을 가지고 있으면 최대시간으로 저장
            if (map[i][j] == 0 && maxtime < time[i][j]) {
                maxtime = time[i][j];
            }
 
            //빈칸인데 시간이 저장되어 있지 않다(바이러스가 퍼지지 못했다)
            if (map[i][j] == 0 && time[i][j] == -1) {
                return -1;
            }
        }
    }
 
    //모두 걸리는데 걸리는 시간을 리턴
    return maxtime;
}
 
void bfs() {
    memset(time, -1sizeof(time));
    queue<pair<intint>> q;
    int x, y;
 
 
    //활성 바이러스들을 큐에 넣어준다.
    for (int i = 0; i < m; i++) {
        x = alive.at(i).first;
        y = alive.at(i).second;
        q.push(make_pair(x, y));
        time[x][y] = 0;
    }
 
 
    int nx, ny;
    while (!q.empty()) {
        x = q.front().first;
        y = q.front().second;
        q.pop();
 
        //인접한 네방향으로 퍼진다.
        for (int k = 0; k < 4; k++) {
            nx = x + dx[k];
            ny = y + dy[k];
 
            //범위 체크
            if (nx < 0 || ny < 0 || nx >= n || ny >= n) continue;
 
            //벽인 경우
            if (map[nx][ny] == 1continue;
 
            //빈 칸에 이미 방문
            if (time[nx][ny] != -1continue;
 
            q.push(make_pair(nx, ny));
            time[nx][ny] = time[x][y] + 1;
        }
 
    }
 
    //check함수로 모두 퍼질 수 없는 경우인 경우 -1, 그렇지 않은 경우 모두 퍼지는데
    //걸리는 시간을 받아온다.
    int tmp = check();
 
    if (tmp == -1) {
        //초기값이라면 -1을 그대로 넣어준다.
        if (ans == 10000000) {
            ans = -1;
        }
    }
    else {
        //받아온 시간이 최솟값이라면 ans에 저장
        //ans가 -1인 경우에도 새로 받아온 값(가능한 경우)로 바꿔줄 수 있다.
        if (ans == -1 || ans > tmp) {
            ans = tmp;
        }
    }
 
 
}
 
 
//활성화 시킬 바이러스 m개를 고른다.
void select(int index, int cnt) {
 
    //m개를 고른 경우
    if (cnt == m) {
        //바이러스를 활성화 시킨다.
        bfs();
        return;
    }
 
    //고를 수 있는 경우를 모두 넘어간 경우 리턴
    if (index >= virus.size()) return;
 
 
    //index번째 바이러스의 좌표를 가져온다.
    int x = virus.at(index).first;
    int y = virus.at(index).second;
 
 
    //index번째 바이러스를 선택한다.
    alive.push_back(make_pair(x, y));
    select(index + 1, cnt + 1);
 
 
    //index번째 바이러스를 선택하지 않는 경우
    alive.pop_back(); //선택하는 경우에서 넣은 거 뺀다
    select(index + 1, cnt);
 
}
 
 
 
int main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> map[i][j];
 
            //바이러스인 경우 벡터에 좌표를 저장
            if (map[i][j] == 2) virus.push_back(make_pair(i, j));
        }
    }
 
    //최솟값을 찾기 위해서 큰 값을 넣어둔다.
    ans = 10000000;
    select(00);
 
    cout << ans << '\n';
 
    //선택하지 않은 바이러스 부분에도 시간 넣어줘야한다!!!!
 
 
    return 0;
}
Colored by Color Scripter
 
 
 

 

 

'BOJ' 카테고리의 다른 글

[BOJ] 17144. 미세먼지 안녕!  (0) 2019.07.30
[BOJ] 17140. 이차원 배열과 연산  (0) 2019.07.30
[BOJ] 1806. 부분합  (0) 2019.07.16
[BOJ] 2003. 수들의 합 2  (0) 2019.07.15
[BOJ] 2748. 피보나치 수 2  (0) 2019.07.15

https://www.acmicpc.net/problem/14501

 

14501번: 퇴사

첫째 줄에 백준이가 얻을 수 있는 최대 이익을 출력한다.

www.acmicpc.net

N의 범위가 1부터 15이므로 완전 탐색으로 쉽게 풀어줄 수 있다.

1부터 N까지의 날짜에 대해 해당 날짜에 상담을 하는 경우하지 않는 경우의 금액을 모두 계산하여 최댓값을 구한다.

주의 할점은 상담할 날로부터의 상담일수가 퇴사하는 날을 넘어가면 안된다.

이부분의 처리만 잘 해주면 된다.

 

자세한 설명은 아래 코드에서!

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
#include <cstdio>
using namespace std;
 
int n;
int max;
int t[16];
int p[16];
 
void solve(int index, int sum) {
    if(index > n) {
 
        //퇴사하는 날짜가 되었으면 최댓값을 계산
 
        if(max < sum) {
            max = sum;
        }
        return;
    }
 
 
    //index번째 날짜에 상담을 하지 않는 경우
    solve(index+1,sum);
 
 
    //퇴사하는 날(n+1)을 넘어가지 않는 경우에만 index번째 날 상담을 진행
    if(index+t[index] <= n+1)
        //상담에 소요되는 날짜와 수익을 더해서 넘겨준다
        solve(index+t[index],sum+p[index]);
 
}
 
int main() {
 
    scanf("%d"&n);
 
    for(int i=1; i<=n; i++) {
        scanf("%d %d"&t[i],&p[i]);
    }
 
 
    max = 0;
    solve(1,0);
    printf("%d", max);
 
    return 0;
}
Colored by Color Scripter
 

'BOJ' 카테고리의 다른 글

[BOJ] 2206. 벽 부수고 이동하기  (0) 2019.06.28
[BOJ] 3055. 탈출  (0) 2019.06.27
[BOJ] 1697. 숨바꼭질  (0) 2019.06.27
[BOJ] 7569. 토마토 (3차원)  (0) 2019.06.27
[BOJ] 7576. 토마토  (0) 2019.06.27

https://www.swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AWXRF8s6ezEDFAUo

 

SW Expert Academy

SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!

www.swexpertacademy.com

 

이 문제에서 어려웠던 부분은 웜홀 부분인 것 같다.

나는 웜홀의 좌표를 저장할 Wormhole클래스를 만들고 배열에 넣어주었다.

2차원 배열에 넣어서 각 6~10 번호마다 두 개씩 이므로 배열의 크기는 5X2가 된다. (웜홀은 쌍으로 존재하므로)

웜홀이 더 적게 존재할수도 있지만 최대 5개밖에 안되므로 그냥 한 번에 배열로 만들어줬다.

웜홀 배열의 각 좌표의 초기값을 -1로 설정해주고 이 점을 이용해서 두 개의 쌍을 차례대로 넣어준다.

 

예전에는 클래스를 만드는 대신에 3차원 int배열로 만들어 줬었다.(5X2X2 크기)

 

 

핀볼의 이동

  1. 출발 위치와 진행 방향을 임의로 설정하여 점수의 최댓값을 구하는 문제이므로 모든 빈 칸(값이 0인 칸)에서 각 네 방향으로 출발해서 모든 경우의 점수를 구한다.
  2. 각 이동에서는 블록의 모양에 따라서 적절히 방향을 잘 바꾸어 주며 이동시키고 블록이나 벽에 부딪힐 경우(범위 벗어나는 경우) 점수를 증가시킨다.
  3. 웜홀을 만난 경우에는 방향을 유지한 채 반대편 웜홀로 이동한다.
  4. 조건에 따라 핀볼이 출발 위치로 돌아오거나 블랙홀에 빠지는 경우가 될때까지 while문을 이용해 핀볼의 이동을 반복한다.
  5. 핀볼의 이동이 끝나면 그때의 점수를 현재 최대 점수와 비교해준다.

 

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

public class Solution {
	
	static class Wormhole {
		int x;
		int y;
		Wormhole(int x, int y) {
			this.x = x;
			this.y = y;
		}
	}
	
	static int N, max;
	static int[][] map;
	static int[] dx = {1,-1,0,0};
	static int[] dy = {0,0,1,-1};
	static Wormhole[][] holeArr;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int T = Integer.parseInt(br.readLine());
		for(int tc=1; tc<=T; tc++) {
			N = Integer.parseInt(br.readLine());
			
			StringTokenizer st;
			map = new int[N][N];
			holeArr = new Wormhole[5][2];
			
            
            //웜홀의 위치 초기화 
			for(int i=0; i<5; i++) {
				for(int j=0; j<2; j++) {
					holeArr[i][j] = new Wormhole(-1,-1);
				}
			}
			
			for(int i=0; i<N; i++) {
				st = new StringTokenizer(br.readLine());
				for(int j=0; j<N; j++) {
					map[i][j] = Integer.parseInt(st.nextToken());
                    
                    //웜홀인 경우
                    //6번 웜홀부터 시작하므로 6번 웜홀이 0번째 index가 된다. 7번이 1, 8번이 2,,, 
					if(map[i][j] >= 6) {
                    
                    	//웜홀 첫 번째
						if(holeArr[map[i][j]-6][0].x == -1 && holeArr[map[i][j]-6][0].y == -1) {
							holeArr[map[i][j]-6][0].x = i;
							holeArr[map[i][j]-6][0].y = j;
						} else { //이미 첫 번째 웜홀의 위치가 저장되어 있는 경우 반대편 웜홀 저장
							holeArr[map[i][j]-6][1].x = i;
							holeArr[map[i][j]-6][1].y = j;
						}
					}
                    
				}
			}			
			
			max = 0;
			for(int i=0; i<N; i++) {
				for(int j=0; j<N; j++) {
                	
                    //빈칸이 아닌경우(블록이나 웜홀, 블랙홀인 경우)
					if(map[i][j] != 0) continue;
					
                    //상하좌우 각 방향으로 출발
					for(int k=0; k<4; k++) {
						move(i,j,k);
					}		
					
				}
			}
			
			System.out.println("#"+tc+" "+max);
		}
	}
	
	static void move(int x, int y, int dir) {
		
		int nx = x;
		int ny = y;
		int point = 0;
		
		while(true) {
        
        	//이동
			nx += dx[dir];
			ny += dy[dir];
			
            
            //벽에 부딪힌 경우 점수 증가하고 방향 전환
			if(nx < 0 || ny < 0 || nx >= N || ny >= N) {
				point++;
				if(dir == 0 || dir == 2) {
					dir++;
				} else {
					dir--;
				}

				continue;
			}
			
            
            //블랙홀에 빠짐
			if(map[nx][ny] == -1) {
				break;
			}
			
            
            //출발위치로 돌아옴
			if(x == nx && y == ny) {
				break;
			}
			
            
            
			if(map[nx][ny] == 0) {
            	//빈칸이므로 방향유지한채 바로 이동
				continue;
			} else if(map[nx][ny] == 1) { //각 블록의 종류에따라서 방향을 변환해주고 점수도 증가
				if(dir == 0) {
					dir = 2;
				} else if(dir == 1) {
					dir = 0;
				} else if(dir == 2) {
					dir = 3;
				} else {
					dir = 1;
				}
				
				point++;
				
			} else if(map[nx][ny] == 2) {
				if(dir == 0) {
					dir = 1;
				} else if(dir == 1) {
					dir = 2;
				} else if(dir == 2) {
					dir = 3;
				} else {
					dir = 0;
				}
				
				point++;
				
			} else if(map[nx][ny] == 3) {
				if(dir == 0) {
					dir = 1;
				} else if(dir == 1) {
					dir = 3;
				} else if(dir == 2) {
					dir = 0;
				} else {
					dir = 2;
				}
				
				point++;
				
			} else if(map[nx][ny] == 4) {
				if(dir == 0) {
					dir = 3;
				} else if(dir == 1) {
					dir = 0;
				} else if(dir == 2) {
					dir = 1;
				} else {
					dir = 2;
				}
				
				point++;
				
			} else if(map[nx][ny] == 5) {
				if(dir == 0 || dir == 2) {
					dir++;
				} else {
					dir--;
				}
				
				point++;
				
			} else { //웜홀인 경우 반대편 웜홀로 이동
				int holenum = map[nx][ny]-6;
				if(nx == holeArr[holenum][0].x && ny == holeArr[holenum][0].y) {
					nx = holeArr[holenum][1].x;
					ny = holeArr[holenum][1].y;
				} else {
					nx = holeArr[holenum][0].x;
					ny = holeArr[holenum][0].y;
				}
			}
		}
		
        
        //이동이 끝난 후에 점수 비교
		if(max < point) {
			max = point;
		}
	}

}

https://www.swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AWIeW7FakkUDFAVH

 

SW Expert Academy

SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!

www.swexpertacademy.com

처리해줘야 할 조건이 꽤 많은 문제였다.

  1. 높이가 같아서 경사로를 안 놓아도 되는지
  2. 높이가 다르다면 높이 차이가 2보다 적게 나서 경사로를 놓아줄 수 있는지
  3. 경사로를 놓을 때 경사로가 범위를 벗어나지 않는지
  4. 경사로를 놓는 곳의 높이는 같은지
  5. 경사로가 이미 놓여져있지는 않은지

 

위의 조건들을 각 행과 열에 대해서 모두 검사해주어야 한다.

모든 행을 검사해주는 checkGaro와 모든 열을 검사해주는 checkSero 함수를 만들어서 테스트 케이스별로 각각 한 번씩 호출해서 답을 구하도록 했다. 각 행이나 열마다 위의 모든 조건을 만족하면 count를 해줬다.

 

 

더 자세한 설명은 코드의 주석 참고

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

public class Solution {

	static int N,X,count;
	static int[][] map;
    
	static boolean[][] garo;
	static boolean[][] sero;
    
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int T = Integer.parseInt(br.readLine());
		for(int tc=1; tc<=T; tc++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			N = Integer.parseInt(st.nextToken());
			X = Integer.parseInt(st.nextToken());
			map = new int[N][N];
            
             //해당 위치에 경사로가 있는지 체크해줄 배열
			garo = new boolean[N][N];
			sero = new boolean[N][N];
			
			for(int i=0; i<N; i++) {
				st = new StringTokenizer(br.readLine());
				for(int j=0; j<N; j++) {
					map[i][j] = Integer.parseInt(st.nextToken());
				}
			}
			
			count = 0;
			
            //모든 행 검사
			checkGaro();
            
            //모든 열 검사
			checkSero();
			
			System.out.println("#"+tc+" "+count);
		}
	}
	
	static void checkGaro() {
		
		
		//모든 행을 검사 
		for(int i=0; i<N; i++) {
			boolean ok = true;
			
			//각 행에서 높이를 검사 
			for(int j=0; j<N-1; j++) {
				
                //높이가 같다면 다음 칸 비교로 넘어간다.
				if(map[i][j] == map[i][j+1]) {
					continue;
				}
				
                //앞의 칸이 뒤의 칸보다 더 큰 경우
				if(map[i][j] > map[i][j+1]) {
					if(map[i][j] - map[i][j+1] > 1) {
						//차이가 1보다 커서 경사로를 놓을 수 없다. 
						ok = false;
						break;
					}
					
					if(j+X >= N) {
						//경사로를 놓으면 범위를 벗어난다.
						ok = false;
						break;
					}
					
					for(int k=j+1; k<=j+X; k++) {
						//경사로를 놓을 칸의 높이가 모두 같은지 검사 
						if(map[i][j+1] != map[i][k]) {
							ok = false;
							break;
						}
						
						//경사로가 이미 놓여져있는지 확인 
						if(garo[i][k]) {
							ok = false;
							break;
						}
					}
					
                    
                    //바로 위의 반복문에서 ok는 false값으로 break되어 나올 경우 break해준다.
					if(!ok) break;
					
                    
					//경사로를 놓는다.
					for(int k=j+1; k<=j+X; k++) {
						garo[i][k] = true;
					}
					
					
					//뒤의 칸이 앞의 칸보다 큰 경우
				} else if(map[i][j] < map[i][j+1]) {
					if(map[i][j+1] - map[i][j] > 1) {
						ok = false;
						break;
					}
					
					if(j+1-X < 0) {
						//경사로를 놓으면 범위를 벗어난다.
						ok = false;
						break;
					}
					
					for(int k=j; k>=j+1-X; k--) {
						//경사로를 놓을 칸의 높이가 모두 같은지 검사 
						if(map[i][j] != map[i][k]) {
							ok = false;
							break;
						}
						
						//경사로가 이미 놓여져있는지 확인 
						if(garo[i][k]) {
							ok = false;
							break;
						}
					}
					
					if(!ok) break;
					
					//경사로를 놓는다.
					for(int k=j; k>=j+1-X; k--) {
						garo[i][k] = true;
					}
				}
	
			}
			
            //해당 행이 모든 조건을 만족하여 ok가 true 값을 가지면 count값을 증가
			if(ok) count++;
		}
	}
	
	static void checkSero() {
		
		//모든 열을 검사 
		for(int j=0; j<N; j++) {
			boolean ok = true;
			
			//각 열에서 높이를 검사 
			for(int i=0; i<N-1; i++) {
				
                //높이가 같다면 다음 칸으로 넘어간다.
				if(map[i][j] == map[i+1][j]) {
					continue;
				}
				
                //앞의 칸이 뒤의 칸보다 더 큰 경우
				if(map[i][j] > map[i+1][j]) {
					if(map[i][j] - map[i+1][j] > 1) {
						//차이가 1보다 커서 경사로를 놓을 수 없다. 
						ok = false;
						break;
					}
					
					if(i+X >= N) {
						//경사로를 놓으면 범위를 벗어난다.
						ok = false;
						break;
					}
					
					for(int k=i+1; k<=i+X; k++) {
						//경사로를 놓을 칸의 높이가 모두 같은지 검사 
						if(map[i+1][j] != map[k][j]) {
							ok = false;
							break;
						}
						
						//경사로가 이미 놓여져있는지 확인 
						if(sero[k][j]) {
							ok = false;
							break;
						}
					}
					
					if(!ok) break;
					
					//경사로를 놓는다.
					for(int k=i+1; k<=i+X; k++) {
						sero[k][j] = true;
					}
					
					
				//뒤의 칸이 앞의 칸보다 더 큰 경우
				} else if(map[i][j] < map[i+1][j]) {
					if(map[i+1][j] - map[i][j] > 1) {
						ok = false;
						break;
					}
					
					if(i+1-X < 0) {
						//경사로를 놓으면 범위를 벗어난다.
						ok = false;
						break;
					}
					
					for(int k=i; k>=i+1-X; k--) {
						//경사로를 놓을 칸의 높이가 모두 같은지 검사 
						if(map[i][j] != map[k][j]) {
							ok = false;
							break;
						}
						
						//경사로가 이미 놓여져있는지 확인 
						if(sero[k][j]) {
							ok = false;
							break;
						}
					}
					
					if(!ok) break;
					
					//경사로를 놓는다.
					for(int k=i; k>=i+1-X; k--) {
						sero[k][j] = true;
					}
				}
						
			}
			
			if(ok) count++;
		}
	}

}

https://www.swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV5V1SYKAaUDFAWu

 

SW Expert Academy

SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!

www.swexpertacademy.com

Fail을 엄청 많이 한 문제였다ㅠㅠ 심지어 이번에 다시 풀어본 것임에도 불구하고 4번 만에 Pass 했다...

보호 필름 문제는 dfs를 사용하여 각 막에 약물 A를 투여하는 경우, B를 투여하는 경우, 약물을 투여하지 않는 경우를 모두 구해줬다.

 

이 문제에서주의할 점은 합격 기준인 K가 1인 경우다.

K가 1인 경우 무조건 합격이므로 검사를 해줄 필요 없이 바로 0을 출력해주어야 한다.

 

완전 탐색

먼저 K==1 인 경우에는 0을 출력해주도록 처리하고 바로 다음 테스트 케이스로 넘어간다.

K가 1보다 큰 경우에는 각 막에 약품을 투여하여 변경된 상태를 새로 적용해주기 위해서 매번 새로운 배열을 만들어주었다.(메모리를 많이 써서 다른 방법을 쓰는 게 좋을 것 같다...)

현재 투여한 약물의 수가 이미 기준을 통과한 최솟값을 넘어간다면 더 검사할 필요가 없으므로 return 하도록 백트래킹을 해준다.

매번 dfs에서는 합격하는지 검사해주고 기준을 통과했다면 min값을 바꾸고 return 한다.(통과한 시점에서 더 많은 약물을 투여할 필요가 없기 때문)

모든 막에 대한 약물 투여 여부를 결정해준 경우에도 return.

 

합격 기준 검사

합격 기준을 검사하는 부분에서는 모든 열에 대해서 합격 기준을 통과하는지 검사해준다.

하나의 열이라도 합격기준을 통과하지 못하면 합격하지 못한다.

각 열에 대해서는 연속하는 K개의 셀이 같은 값인지 검사해주고 한 번이라도 그러한 경우가 있다면 다음 열의 검사로 넘어간다.

 

import java.io.*;
import java.util.StringTokenizer;
 
public class Solution {
 
    static int D,W,K, min;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(br.readLine());
        for(int tc=1; tc<=T; tc++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            D = Integer.parseInt(st.nextToken());
            W = Integer.parseInt(st.nextToken());
            K = Integer.parseInt(st.nextToken());
 
            int[][] film = new int[D][W];
            for(int i=0; i<D; i++) {
                st = new StringTokenizer(br.readLine());
                for(int j=0; j<W; j++) {
                    film[i][j] = Integer.parseInt(st.nextToken());
                }
            }
 
 			
            //합격 기준이 1인 경우 바로 0을 출력하고 다음 테스트 케이스로 넘어간다.
            if(K == 1) {
                System.out.println("#"+tc+" "+0);
                continue;
            }
             
             
            min = D+1; //약물을 투여하는 최대 개수는 D개이므로 적당히 D+1정도로 설정해주었다.
            dfs(film,0,0);
            System.out.println("#"+tc+" "+min);
        }
    }
     
    static void dfs(int[][] film, int index, int count) {
    	//현재 약품 투여 횟수가 이미 최솟값을 넘은 경우 더 검사할 필요가 없으므로 return
        if(count >= min) return; 
        
        
        //합격 기준을 통과한 경우 최솟값을 count값(현재 약품 투여 횟수)으로 변경하고 return
        if(test(film)) {
            min = count;
            return;
        }
        
        
        //막의 범위를 넘어가는 경우 return
        if(index == D) return;
         
         
        //새로 넘겨줄 배열 생성
        int[][] arr = new int[D][W];
        for(int i=0; i<D; i++) {
            for(int j=0; j<W; j++) {
                arr[i][j] = film[i][j];
            }
        }
         
         
        //index행에 약품을 투여하지 않는 경우
        dfs(arr,index+1,count);
         
         
        //index행에 A약품을 투여하는 경우
        for(int j=0; j<W; j++) {
            arr[index][j] = 0;
        }
        dfs(arr,index+1, count+1);
        
        
        //index행에 B약품을 투여하는 경우
        for(int j=0; j<W; j++) {
            arr[index][j] = 1;
        }
        dfs(arr,index+1, count+1);
         
    }
     
    static boolean test(int[][] film) {
        boolean flag = false;
 
 		//모든 열을 검사 
        for(int j=0; j<W; j++) {
        
        	//각 열의 셀을 모두 검사 
            for(int i=0; i<=D-K; i++) {
                flag = true;
                
                //각 열에서 K개만큼의 연속적인 셀을 검사 
                for(int l=i+1; l<i+K; l++) {
                    if(film[i][j] != film[l][j]) {
                        flag = false; //K개만큼 연속적으로 같지 않으면 break 하고 밑의 셀부터 다시 검사 
                        break;
                    }
                }
                 
                //K개 연속적으로 같은 셀이 있으면 한 번이라도 있으면 이번 열은 바로 통과하고 다음 열로 넘어간다.
                if(flag) break;                 
            }
             
            //이번 검사한 열이 검사를 통과하지 못하므로 아예 테스트 통과 불가능 
            if(!flag) break;
        }
         
        if(!flag)
            return false;
        else
            return true;
    }
     
}

https://www.swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV5VwAr6APYDFAWu

 

SW Expert Academy

SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!

www.swexpertacademy.com


디저트 카페 문제는 아래 그림처럼 사각형 모양으로 움직여서 원점으로 돌아와야 한다.

원안의 숫자(디저트의 종류)가 겹치면 안 되며 범위를 벗어나도 안된다

더 자세한 조건은 문제 사이트 참고!
 

 

풀이

먼저 N의 범위가 크지 않으므로 모든 경우를 해볼 수 있다. 따라서 DFS를 이용해서 풀었다.

  1. 각 칸을 시작점으로 하는 사각형을 모두 만들어준다. 
  2. 사각형을 만들 수 없는 N-1 행과 N-2행, 0열과 N-1열은 시작점에서 제외한다.

 

move 함수로 DFS를 해주는데 조건을 잘 처리해주어야 한다.

  1. 방향은 오른쪽 아래 방향(1,1), 왼쪽 아래 방향(1,-1), 왼쪽 위 방향(-1,-1), 오른쪽 위 방향(-1,1)의 순서를 정해놓고 움직인다.
  2. 이동시킨 곳이 범위를 벗어나지 않아야 하고 방향은 3(시작점으로 돌아가는 마지막 방향)을 넘지 않는다.
  3. 움직일 때마다 해당 디저트 종류를 ArrayList에 추가하였다. 계속 탐색을 진행하면서 디저트 종류가 ArrayList에 이미 포함되지 않은 경우에만 탐색을 계속 진행한다.(같은 디저트를 먹지 않는 조건)
  4. 탐색을 할 때는 현재 방향을 유지해서 이동하는 경우방향을 꺾는 경우(dir 인덱스 증가)를 해주었다.
  5. 탐색 시작점에서는 현재 방향을 유지하는 경우만 해줘야 한다(직사각형 모양을 위해 바로 꺾지 못함)
  6. 현재 방향이 마지막 방향(오른쪽 위)이고 시작점에 돌아왔으면 max값과 디저트 종류를 추가한 ArrayList의 사이즈를 비교한 뒤 return 한다.

 

코드 주석에 설명 추가

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

public class Solution {
	
	static int N, max, sx, sy;
	static int[][] map;
	static int[] dx = {1,1,-1,-1};
	static int[] dy = {1,-1,-1,1};
	static ArrayList list;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int T = Integer.parseInt(br.readLine());
		StringTokenizer st;
		for(int tc=1;tc<=T;tc++) {
			N = Integer.parseInt(br.readLine());
			map = new int[N][N];
			
			for(int i=0; i<N; i++) {
				st = new StringTokenizer(br.readLine());
				for(int j=0; j<N; j++) {
					map[i][j] = Integer.parseInt(st.nextToken());
				}
			}
            
            
			list = new ArrayList(); //디저트 종류를 추가할 list
			max = -1; //디저트를 먹을 수 없는 경우 -1을 출력하기 위해 -1로 할당
            
            
            //사각형을 만들 수 없는 곳을 제외한 모든 칸에서 탐색 시작
			for(int i=0; i<N-2; i++) {
				for(int j=1; j<N-1; j++) {
					sx = i;
					sy = j;
					list.add(map[sx][sy]);
					move(sx,sy,0);
					list.clear(); //다음 칸에서 출발하는 경우를 위해 list를 비워준다.
				}
			}
			
			System.out.println("#"+tc+" "+max);
		}
	}
	
    
    //DFS
	static void move(int i, int j, int dir) {
		int x;
		int y;

		
        //현재 방향을 유지하며 이동하는 경우
		x = i+dx[dir];
		y = j+dy[dir];
		if(x >= 0 && y >= 0 && x < N && y < N) {
        
			if(dir == 3 && x == sx && y == sy) {
            	//사각형을 만들고 시작점으로 돌아온 경우
				if(max < list.size()) {
					max = list.size();
				}
				return;
			}
            
            //이동할 칸의 디저트가 list에 포함되어 있지 않아야한다.
			if(!list.contains(map[x][y])) {
				list.add(map[x][y]);
				move(x,y,dir);
				list.remove(list.size()-1);
			}
		}
		
		
        //방향을 꺾는 경우에는 dir+1을 해준 값으로 계산
		if(dir+1 == 4) return; //방향은 최대 3이므로
		if(dir == 0 &&i == sx && j == sy) return; //시작점에서는 꺾을 수 없다.
		x = i+dx[dir+1];
		y = j+dy[dir+1];
		
		if(x >= 0 && y >= 0 && x < N && y < N) {
        
			if(dir+1 == 3 && x == sx && y == sy) {
            	//사각형을 만들고 시작점으로 돌아온 경우
				if(max < list.size()) {
					max = list.size();
				}
				return;
			}
            
            //이동할 칸의 디저트가 list에 포함되어 있지 않아야한다.
			if(!list.contains(map[x][y])) {
				list.add(map[x][y]);
				move(x,y,dir+1);
				list.remove(list.size()-1);
			}
		}		
	}

}

 

 

더 빠른 시간안에 문제를 해결한 사람들의 코드를 보면 dfs를 사용하지 않고 반복문만을 사용하였다. 속도가 거의 두배 넘게 차이가 나는 것 같다. 물론 백트랙킹을 더 잘해주면 차이가 더 적게 날 수도 있을 것 같다... 다음에 dfs를 사용하지 않고 다시 풀어봐야겠다.

+ Recent posts