반응형

문제 링크: https://www.acmicpc.net/problem/21608

 

21608번: 상어 초등학교

상어 초등학교에는 교실이 하나 있고, 교실은 N×N 크기의 격자로 나타낼 수 있다. 학교에 다니는 학생의 수는 N2명이다. 오늘은 모든 학생의 자리를 정하는 날이다. 학생은 1번부터 N2번까지 번호

www.acmicpc.net

<문제 풀이> 우선순위큐, SET, 구현

 

아래와 같이 SET을 사용하면 인접한 칸에 있는 학생들이 i번 학생이 좋아하는 학생인지를 쉽게 찾을 수 있습니다.

unordered_set<int> prefer[401]; //prefer[i] : i번 학생이 좋아하는 학생들의 번호

인접한 칸에 있는 학생이 좋아하는 학생인지 확인할 때 perfer[i]에 값이 존재하는지만 확인하면 됩니다.

 

 

아래와 같이 우선순위큐를 사용하면 학생들의 자리를 쉽게 배정할 수 있습니다.

 

struct cmp {
	bool operator()(const Position& a, const Position& b) {
		if (a.preferCnt == b.preferCnt) {
			if (a.blankCnt == b.blankCnt) {
				if (a.r == b.r) { //4. 3을 만족하는 칸도 여러 개 인경우 열의 번호가 가장 작은 칸으로 자리를 정한다.
					return a.c > b.c; 
				}
				else { // 3. 2를 만족하는 칸도 여러 개인 경우 행의 번호가 가장 작은 칸으로 자리를 정한다.
					return a.r > b.r;
				}
			}
			else { // 2. 1을 만족하는 칸이 여러 개이면, 비어있는 칸이 가장 많은 칸으로 자리를 정한다.
				return a.blankCnt < b.blankCnt;
			}
		}
		else { // 1. 좋아하는 학생이 인접한 칸에 가장 많은 칸으로 자리를 정한다.
			return a.preferCnt < b.preferCnt; 
		}
	}
};


priority_queue<Position, vector<Position>, cmp> pq;

 

 

 

<전체 C++소스코드>

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
#include <iostream>
#include <unordered_set>
#include <queue>
#include <vector>
using namespace std;
 
int n;
int board[21][21];
unordered_set<int> prefer[401]; //prefer[i] : i번 학생이 좋아하는 학생들의 번호
vector<int> students;
const int dx[4= { 001-1 };
const int dy[4= { 1-100 };
const int satisfaction[5= { 01101001000 };
 
class Position {
public:
    int preferCnt;
    int blankCnt;
    int r, c;
    Position(int preferCnt, int blankCnt, int r, int c) {
        this->preferCnt = preferCnt;
        this->blankCnt = blankCnt;
        this->= r;
        this->= c;
    }
};
 
struct cmp {
    bool operator()(const Position& a, const Position& b) {
        if (a.preferCnt == b.preferCnt) {
            if (a.blankCnt == b.blankCnt) {
                if (a.r == b.r) { //4. 3을 만족하는 칸도 여러 개 인경우 열의 번호가 가장 작은 칸으로 자리를 정한다.
                    return a.c > b.c; 
                }
                else { // 3. 2를 만족하는 칸도 여러 개인 경우 행의 번호가 가장 작은 칸으로 자리를 정한다.
                    return a.r > b.r;
                }
            }
            else { // 2. 1을 만족하는 칸이 여러 개이면, 비어있는 칸이 가장 많은 칸으로 자리를 정한다.
                return a.blankCnt < b.blankCnt;
            }
        }
        else { // 1. 좋아하는 학생이 인접한 칸에 가장 많은 칸으로 자리를 정한다.
            return a.preferCnt < b.preferCnt; 
        }
    }
};
 
void input() {
    cin >> n;
    for (int i = 1; i <= n * n; i++) {
        int p, a, b, c, d;
        cin >> p >> a >> b >> c >> d;
        prefer[p].insert(a);
        prefer[p].insert(b);
        prefer[p].insert(c);
        prefer[p].insert(d);
        students.push_back(p);
    }
}
 
 
void select() {
    for(auto student : students){ // 학생을 한 명씩 배치한다.
        priority_queue<Position, vector<Position>, cmp> pq;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (board[i][j]) continue;
                int preferCnt = 0;
                int blankCnt = 0;
                for (int dir = 0; dir < 4; dir++) {
                    int nx = i + dx[dir];
                    int ny = j + dy[dir];
                    if (nx < 1 || ny < 1 || nx > n || ny > n) continue;
                    if (board[nx][ny] == 0) blankCnt++;
                    else if (prefer[student].find(board[nx][ny]) != prefer[student].end()) preferCnt++// 인접한 칸 중에서 종아하는 학생들의 수
                }
                pq.push({ preferCnt, blankCnt, i, j });
            }
        }
        if (!pq.empty()) {
            auto target = pq.top(); // 우선순위 큐의 top에 있는 좌표가 학생의 자리
            board[target.r][target.c] = student;
        }
    }
}
 
 
int getTotalSatisfaction() {
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            int preferCnt = 0;
            int student = board[i][j];
            for (int dir = 0; dir < 4; dir++) {
                int nx = i + dx[dir];
                int ny = j + dy[dir];
                if (nx < 1 || ny < 1 || nx > n || ny > n) continue;
                if (prefer[student].find(board[nx][ny]) != prefer[student].end()) preferCnt++;
            }
            ans += satisfaction[preferCnt];
 
        }
    }
    return ans;
}
 
 
void solve() {
    input();
    select();
    cout << getTotalSatisfaction();
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
 
    solve();
    
 
    return 0;
}
cs
 

 

반응형
반응형

문제 링크: https://www.acmicpc.net/problem/23289

 

23289번: 온풍기 안녕!

유난히 추운 날씨가 예상되는 이번 겨울을 대비하기 위해 구사과는 온풍기를 설치하려고 한다. 온풍기의 성능을 테스트하기 위해 구사과는 집을 크기가 R×C인 격자판으로 나타냈고, 1×1 크기

www.acmicpc.net

<문제 풀이> BFS, 시뮬레이션

 

0. 입력

온도, 온풍기, 조사하는 칸을 각각 2차원 배열로 표현했습니다.

int temperature[21][21];
int heater[21][21];
int inspection[21][21];

(a, b)와 (c, d) 사이에 벽이 있다는 것을 표현하기 위해서 다음과 같이 4차원 배열을 만들었습니다.

bool wall[21][21][21][21]; //wall[a][b][c][d] : (a,b)에서 (c,d) 방향으로 바람을 막는 벽이 존재하면 true

wall[a][b][c][d]가 참이면 (a, b)에서 (c, d) 방향으로 바람을 막는 벽이 존재합니다.

wall[c][d][a][b]가 참이면 (c, d)에서 (a, b) 방향으로 바람을 막는 벽이 존재합니다.

위 두 가지를 표현함으로써 (a,b)와 (c, d) 사이에 벽을 만들 수 있습니다.

 

int w; cin >> w;
	for (int i = 0; i < w; i++) {
		int x, y, t; cin >> x >> y >> t;
		if (t == 0) {
			wall[x][y][x - 1][y] = true;
			wall[x - 1][y][x][y] = true;
		}
		else {
			wall[x][y][x][y + 1] = true;
			wall[x][y + 1][x][y] = true;
		}
	}

 

 

1. 집에 있는 모든 온풍기에서 바람이 한 번 나옴

 

오른쪽으로 바람이 불 때 각 칸에 있는 바람은 오른쪽 위, 오른쪽, 오른쪽 아래 방향으로 한 칸 이동하게 됩니다.

이때 현재 칸과 이동하려는 칸 사이에 벽이 존재한다면 이동할 수 없습니다.

이동할 수 있다면 다음 칸으로 이동하면 되는 데, 다음 칸의 온도 값은 (현재 칸의 온도 값 - 1)이 됩니다.

 

(x, y)에서 오른쪽인 (x, y + 1)으로 바람이 이동할 수 있는지는 판단하는 방법은 wall[x][y][x][y+1]의 값이 true인지만 확인하면 알 수 있습니다

그러나 (x, y)에서 오른쪽 위인 (x -1, y 1), 오른쪽 아래인 (x + 1, y + 1)으로 바람이 이동할 수 있는지 판별하는 것은 두 좌표만 가지고는 알 수 없습니다.

 

이 부분은 대각선 방향으로 바로 이동하는 것이 아닌 좌표를 하나 경유해서 이동하면 쉽게 해결할 수 있습니다.

 

아래 그림을 보면

 

 

(x, y)에서 오른쪽 위로 가기 위해서 (x - 1, y)를 경유하고, 오른쪽 아래로 가기 위해서 (x + 1, y)를 경유합니다.

 

대각선 방향인 오른쪽 위로 가기 위해서 아래 두 가지를 체크해보면 됩니다.

 

1) (x, y)와 (x - 1, y) 사이에 벽이 있는가? => wall[x][y][x-1][y]

2) (x - 1, y)와 (x -1, y + 1) 사이에  벽이 있는가? => wall[x-1][y][x-1][y+1]

 

(x -1, y)와 (x - 1, y + 1) 사이에 벽이 있기 때문에 결과적으로 오른쪽 위로 갈 수 없습니다.

 

추가적으로 현재 좌표의 온도가 2 이상일 경우에만 바람이 이동하도록 하면 됩니다. 현재 좌표의 온도가 1이라면 다음 좌표부터는 온도가 0 이하가 되기 때문이 이동할 필요가 없습니다.

 

위 내용들을 바탕으로 오른쪽 세 방향으로 이동하는 BFS를 구현하면 됩니다.

바람의 온도는 5부터 시작하고 온풍기 하나가 만든 온도는 임시 배열에 저장했다가 마지막에 원본 온도 배열에 누적하면 됩니다.

 

아래는 오른쪽 방향을 구현한 코드이고 왼쪽, 아래쪽, 위쪽 방향도 오른쪽 방향 구현과 같은 식으로 하면 됩니다.

 

// 1. 집에 있는 모든 온풍기에서 바람이 한 번 나옴
bool check(int cx, int cy, int nx, int ny) {
	return !(nx < 1 || ny < 1 || nx>R || ny > C || wall[cx][cy][nx][ny]);
}

void rightHeat(int x, int y) {
	queue<pair<int, int > >Q;
	int temp[21][21];
	fill(&temp[0][0], &temp[20][21], 0);

	Q.push({ x, y + 1 });
	temp[x][y + 1] = 5;
	while (!Q.empty()) {
		auto cur = Q.front(); Q.pop();
		// (x,y)에서 위 칸을 경유해서 오른쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
		if (check(cur.X, cur.Y, cur.X - 1, cur.Y) && check(cur.X - 1, cur.Y, cur.X - 1, cur.Y + 1) && temp[cur.X][cur.Y] >= 2) {
			Q.push({ cur.X - 1, cur.Y + 1 }); // 바람을 다음 칸으로 이동
			temp[cur.X - 1][cur.Y + 1] = temp[cur.X][cur.Y] - 1;
		}
		// (x, y)에서 오른쪽으로 갈 수 있으면서 (x,y)에 온도가 2 이상이면
		if (check(cur.X, cur.Y, cur.X, cur.Y + 1) && temp[cur.X][cur.Y] >= 2) {
			Q.push({ cur.X, cur.Y + 1 }); // 바람을 다음 칸으로 이동
			temp[cur.X][cur.Y + 1] = temp[cur.X][cur.Y] - 1;
		}
		// (x, y)에서 아래 칸을 경유해서 오른쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
		if (check(cur.X, cur.Y, cur.X + 1, cur.Y) && check(cur.X + 1, cur.Y, cur.X + 1, cur.Y + 1) && temp[cur.X][cur.Y] >= 2) {
			Q.push({ cur.X + 1, cur.Y + 1 }); // 바람을 다음 칸으로 이동
			temp[cur.X + 1][cur.Y + 1] = temp[cur.X][cur.Y] - 1;
		}
	}
	for (int i = 1; i <= R; i++) {
		for (int j = 1; j <= C; j++) {
			temperature[i][j] += temp[i][j];
		}
	}

}


void heat() {
	for (int i = 1; i <= R; i++) {
		for (int j = 1; j <= C; j++) {
			if (heater[i][j]) {
				if (heater[i][j] == RIGHT) {
					rightHeat(i, j);
				}
				else if (heater[i][j] == LEFT) {
					leftHeat(i, j);
				}
				else if (heater[i][j] == UP) {
					upHeat(i, j);

				}
				else if (heater[i][j] == DOWN) {
					downHeat(i, j);
				}
			}
		}
	}
}

 

2. 온도가 조절됨

 

온도 조절은 단순히 각 칸에 대해서 남, 동 방향으로 온도를 조절하면 됩니다. 동, 서, 남, 북을 모두 하지 않는 이유는 한 번 조절한 두 칸 사이에는 온도를 다시 조절하면 안 되기 때문입니다. 남, 동 방향으로만 온도 조절하면 중복 없이 인접한 칸에 대해서 온도 조절을 할 수 있습니다.

 

한 가지 주의할 점은 온도는 동시에 조절되기 때문에 임시 변수에 저장해 두었다가 실제 배열에 반영해야 합니다.

 

// 2. 온도가 조절됨
void adjust() {
	int temp[21][21];
	fill(&temp[0][0], &temp[20][21], 0);
	for (int i = 1; i <= R; i++) {
		for (int j = 1; j <= C; j++) {
			for (int dir = 0; dir < 2; dir++) { //온도 조절은 남, 동 방향으로만 하면 됨(한 번 조절한 두 칸 사이에는 다시 조절하면 안 되기 때문)
				int nx = i + adjustDx[dir];
				int ny = j + adjustDy[dir];
				if (nx < 1 || ny < 1 || nx > R || ny > C) continue;
				if (wall[i][j][nx][ny] || wall[nx][ny][i][j]) continue; // 두 좌표 사이에 벽이 있으면 온도를 조절하지 않는다.
				int L = abs(temperature[i][j] - temperature[nx][ny]);
				if (temperature[i][j] > temperature[nx][ny]) {
					temp[i][j] -= L / 4;
					temp[nx][ny] += L / 4;
				}
				else if (temperature[i][j] < temperature[nx][ny]) {
					temp[i][j] += L / 4;
					temp[nx][ny] -= L / 4;
				}
			}
		}
	}
	//온도는 동시에 조절되기 때문에 임시 변수에 저장해두었다가 실제 배열에 반영해야함.
	for (int i = 1; i <= R; i++) {
		for (int j = 1; j <= C; j++) {
			temperature[i][j] += temp[i][j];
			if (temperature[i][j] < 0) temperature[i][j] = 0;
		}
	}
}

 

3. 온도가 1 이상인 가장 바깥쪽 칸의 온도가 1씩 감소

 

모든 칸을 순회하면서 각 좌표가 2 <=i <=R-1 and 2 <= j <= C - 1이면 무시하고 그렇지 않으면 온도를 1 감소시키면 됩니다. 

 

// 3. 온도가 1 이상인 가장 바깥쪽 칸의 온도가 1씩 감소
void decreseBorderTemperature() {
	for (int i = 1; i <= R; i++) {
		for (int j = 1; j <= C; j++) {
			if (i >= 2 && i <= R - 1 && j >= 2 && j <= C - 1) continue; // 안쪽 칸이면 무시
			if (temperature[i][j]) temperature[i][j]--;
		}
	}
}

 

4. 초콜릿을 하나 먹는다.

 

// 4. 초콜릿을 하나 먹는다.
void eat() {
	chocolate++;
}

 

5. 조사하는 모든 칸의 온도가 K 이상이 되었는지 검사. 모든 칸의 온도가 K 이상이면 테스트 중단, 아니면 1부터 다시 시작

 

// 5. 조사하는 모든 칸의 온도가 K 이상이 되었는지 검사. 모든 칸의 온도가 K 이상이면 테스트 중단, 아니면 1부터 다시 시작
bool stop() {
	if (chocolate > 100) return true; // 초콜릿을 101개 이상 먹었으면 테스트 종료
	for (int i = 1; i <= R; i++) {
		for (int j = 1; j <= C; j++) {
			if (inspection[i][j] && temperature[i][j] < K) return false;
		}
	}
	return true;
}

 

 

6. solve 함수

void solve() {
	input();
	do {
		heat(); // 1. 집에 있는 모든 온풍기에서 바람이 한 번 나옴
		adjust(); // 2. 온도가 조절됨
		decreseBorderTemperature(); // 3. 온도가 1 이상인 가장 바깥쪽 칸의 온도가 1씩 감소
		eat(); // 4. 초콜릿을 하나 먹는다.
	} while (!stop()); // 5. 조사하는 모든 칸의 온도가 K 이상이 되었는지 검사. 모든 칸의 온도가 K이상이면 테스트를 중단하고, 아니면 1부터 다시 시작한다.
	cout << chocolate;

}

 

 

<전체 C++소스코드>

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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
 
#define X first
#define Y second
 
#define RIGHT 1
#define LEFT 2
#define UP 3
#define DOWN 4
 
int R, C, K;
 
int temperature[21][21]; // 온도를 표현하는 배열
int heater[21][21]; // 온풍기를 표현하는 배열
bool inspection[21][21]; // 온도를 조사해야 하는 칸이면 true
int chocolate = 0;
bool wall[21][21][21][21]; //wall[a][b][c][d] : (a,b)에서 (c,d) 방향으로 바람을 막는 벽이 존재하면 true
const int dx[5= { 000-11 };
const int dy[5= { 01-100 };
const int adjustDx[2= {10}; // 온도 조절은 남, 동 방향으로만 하면 된다.
const int adjustDy[2= {01};
void input() {
    cin >> R >> C >> K;
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            int value; cin >> value;
            if (value == 5) inspection[i][j] = true;
            else if (value == 0)temperature[i][j] = 0;
            else heater[i][j] = value;
 
        }
    }
    int w; cin >> w;
    for (int i = 0; i < w; i++) {
        int x, y, t; cin >> x >> y >> t;
        if (t == 0) {
            wall[x][y][x - 1][y] = true;
            wall[x - 1][y][x][y] = true;
        }
        else {
            wall[x][y][x][y + 1= true;
            wall[x][y + 1][x][y] = true;
        }
    }
}
 
// 1. 집에 있는 모든 온풍기에서 바람이 한 번 나옴
bool check(int cx, int cy, int nx, int ny) {
    return !(nx < 1 || ny < 1 || nx>|| ny > C || wall[cx][cy][nx][ny]);
}
 
void rightHeat(int x, int y) {
    queue<pair<intint > >Q;
    int temp[21][21];
    fill(&temp[0][0], &temp[20][21], 0);
 
    Q.push({ x, y + 1 });
    temp[x][y + 1= 5;
    while (!Q.empty()) {
        auto cur = Q.front(); Q.pop();
        // (x,y)에서 위 칸을 경유해서 오른쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X - 1, cur.Y) && check(cur.X - 1, cur.Y, cur.X - 1, cur.Y + 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X - 1, cur.Y + 1 }); // 바람을 다음 칸으로 이동
            temp[cur.X - 1][cur.Y + 1= temp[cur.X][cur.Y] - 1;
        }
        // (x, y)에서 오른쪽으로 갈 수 있으면서 (x,y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X, cur.Y + 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X, cur.Y + 1 }); // 바람을 다음 칸으로 이동
            temp[cur.X][cur.Y + 1= temp[cur.X][cur.Y] - 1;
        }
        // (x, y)에서 아래 칸을 경유해서 오른쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X + 1, cur.Y) && check(cur.X + 1, cur.Y, cur.X + 1, cur.Y + 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X + 1, cur.Y + 1 }); // 바람을 다음 칸으로 이동
            temp[cur.X + 1][cur.Y + 1= temp[cur.X][cur.Y] - 1;
        }
    }
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            temperature[i][j] += temp[i][j];
        }
    }
 
}
void leftHeat(int x, int y) {
    queue<pair<intint > >Q;
    int temp[21][21];
    fill(&temp[0][0], &temp[20][21], 0);
 
    Q.push({ x, y - 1 });
    temp[x][y - 1= 5;
    while (!Q.empty()) {
        auto cur = Q.front(); Q.pop();
        // (x,y)에서 위 칸을 경유해서 왼쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X - 1, cur.Y) && check(cur.X - 1, cur.Y, cur.X - 1, cur.Y - 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X - 1, cur.Y - 1 });
            temp[cur.X - 1][cur.Y - 1= temp[cur.X][cur.Y] - 1;
        }
        // (x, y)에서 왼쪽으로 갈 수 있으면서 (x,y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X, cur.Y - 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X, cur.Y - 1 });
            temp[cur.X][cur.Y - 1= temp[cur.X][cur.Y] - 1;
        }
        // (x,y)에서 아래 칸을 경유해서 왼쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X + 1, cur.Y) && check(cur.X + 1, cur.Y, cur.X + 1, cur.Y - 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X + 1, cur.Y - 1 });
            temp[cur.X + 1][cur.Y - 1= temp[cur.X][cur.Y] - 1;
        }
    }
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            temperature[i][j] += temp[i][j];
        }
    }
}
void upHeat(int x, int y) {
    queue<pair<intint > >Q;
    int temp[21][21];
    fill(&temp[0][0], &temp[20][21], 0);
    Q.push({ x - 1, y });
    temp[x - 1][y] = 5;
    while (!Q.empty()) {
        auto cur = Q.front(); Q.pop();
        // (x,y)에서 왼쪽 칸을 경유해서 위쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X, cur.Y - 1&& check(cur.X, cur.Y - 1, cur.X - 1, cur.Y - 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X - 1, cur.Y - 1 });
            temp[cur.X - 1][cur.Y - 1= temp[cur.X][cur.Y] - 1;
        }
        // (x, y)에서 위쪽으로 갈 수 있으면서 (x,y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X - 1, cur.Y) && temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X - 1, cur.Y });
            temp[cur.X - 1][cur.Y] = temp[cur.X][cur.Y] - 1;
        }
        // (x,y)에서 오른쪽 칸을 경유해서 위쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X, cur.Y + 1&& check(cur.X, cur.Y + 1, cur.X - 1, cur.Y + 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X - 1, cur.Y + 1 });
            temp[cur.X - 1][cur.Y + 1= temp[cur.X][cur.Y] - 1;
        }
    }
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            temperature[i][j] += temp[i][j];
        }
    }
}
void downHeat(int x, int y) {
    queue<pair<intint > >Q;
    int temp[21][21];
    fill(&temp[0][0], &temp[20][21], 0);
 
    Q.push({ x + 1, y });
    temp[x + 1][y] = 5;
    while (!Q.empty()) {
        auto cur = Q.front(); Q.pop();
        // (x,y)에서 왼쪽 칸을 경유해서 아래쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X, cur.Y - 1&& check(cur.X, cur.Y - 1, cur.X + 1, cur.Y - 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X + 1, cur.Y - 1 });
            temp[cur.X + 1][cur.Y - 1= temp[cur.X][cur.Y] - 1;
        }
        // (x, y)에서 아래쪽으로 갈 수 있으면서 (x,y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X + 1, cur.Y) && temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X + 1, cur.Y });
            temp[cur.X + 1][cur.Y] = temp[cur.X][cur.Y] - 1;
        }
        // (x,y)에서 오른쪽 칸을 경유해서 아래쪽으로 갈 수 있고 (x, y)에 온도가 2 이상이면
        if (check(cur.X, cur.Y, cur.X, cur.Y + 1&& check(cur.X, cur.Y + 1, cur.X + 1, cur.Y + 1&& temp[cur.X][cur.Y] >= 2) {
            Q.push({ cur.X + 1, cur.Y + 1 });
            temp[cur.X + 1][cur.Y + 1= temp[cur.X][cur.Y] - 1;
        }
    }
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            temperature[i][j] += temp[i][j];
        }
    }
}
void heat() {
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            if (heater[i][j]) {
                if (heater[i][j] == RIGHT) {
                    rightHeat(i, j);
                }
                else if (heater[i][j] == LEFT) {
                    leftHeat(i, j);
                }
                else if (heater[i][j] == UP) {
                    upHeat(i, j);
 
                }
                else if (heater[i][j] == DOWN) {
                    downHeat(i, j);
                }
            }
        }
    }
}
 
 
// 2. 온도가 조절됨
void adjust() {
    int temp[21][21];
    fill(&temp[0][0], &temp[20][21], 0);
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            for (int dir = 0; dir < 2; dir++) { //온도 조절은 남, 동 방향으로만 하면 됨(한 번 조절한 두 칸 사이에는 다시 조절하면 안 되기 때문)
                int nx = i + adjustDx[dir];
                int ny = j + adjustDy[dir];
                if (nx < 1 || ny < 1 || nx > R || ny > C) continue;
                if (wall[i][j][nx][ny] || wall[nx][ny][i][j]) continue// 두 좌표 사이에 벽이 있으면 온도를 조절하지 않는다.
                int L = abs(temperature[i][j] - temperature[nx][ny]);
                if (temperature[i][j] > temperature[nx][ny]) {
                    temp[i][j] -= L / 4;
                    temp[nx][ny] += L / 4;
                }
                else if (temperature[i][j] < temperature[nx][ny]) {
                    temp[i][j] += L / 4;
                    temp[nx][ny] -= L / 4;
                }
            }
        }
    }
    //온도는 동시에 조절되기 때문에 임시 변수에 저장해두었다가 실제 배열에 반영해야함.
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            temperature[i][j] += temp[i][j];
            if (temperature[i][j] < 0) temperature[i][j] = 0;
        }
    }
}
 
// 3. 온도가 1 이상인 가장 바깥쪽 칸의 온도가 1씩 감소
void decreseBorderTemperature() {
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            if (i >= 2 && i <= R - 1 && j >= 2 && j <= C - 1continue// 안쪽 칸이면 무시
            if (temperature[i][j]) temperature[i][j]--;
        }
    }
}
 
// 4. 초콜릿을 하나 먹는다.
void eat() {
    chocolate++;
}
 
 
 
// 5. 조사하는 모든 칸의 온도가 K 이상이 되었는지 검사. 모든 칸의 온도가 K 이상이면 테스트 중단, 아니면 1부터 다시 시작
bool stop() {
    if (chocolate > 100return true// 초콜릿을 101개 이상 먹었으면 테스트 종료
    for (int i = 1; i <= R; i++) {
        for (int j = 1; j <= C; j++) {
            if (inspection[i][j] && temperature[i][j] < K) return false;
        }
    }
    return true;
}
 
void solve() {
    input();
    do {
        heat(); // 1. 집에 있는 모든 온풍기에서 바람이 한 번 나옴
        adjust(); // 2. 온도가 조절됨
        decreseBorderTemperature(); // 3. 온도가 1 이상인 가장 바깥쪽 칸의 온도가 1씩 감소
        eat(); // 4. 초콜릿을 하나 먹는다.
    } while (!stop()); // 5. 조사하는 모든 칸의 온도가 K 이상이 되었는지 검사. 모든 칸의 온도가 K이상이면 테스트를 중단하고, 아니면 1부터 다시 시작한다.
    cout << chocolate;
 
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
 
    solve();
 
    return 0;
}
cs
 
반응형
반응형

문제 링크: https://www.acmicpc.net/problem/1938

 

1938번: 통나무 옮기기

첫째 줄에 주어진 평지의 한 변의 길이 N이 주어진다. (4 ≤ N ≤ 50) 주어진다. 이어서 그 지형의 정보가 0, 1, B, E로 이루어진 문자열로 주어진다. 한 줄에 입력되는 문자열의 길이는 N이며 입력 문

www.acmicpc.net

<문제 풀이> BFS 알고리즘

 

이 문제는 통나무의 중심 좌표를 가지고 목적지까지 최단거리로 이동하는 BFS를 구현하면 됩니다.

 

통나무는 현재 자신이 있는 좌표에서 BFS로 다음 좌표로 이동할 때 아래 두 가지 동작을 합니다.

1. 현재 좌표에 있는 통나무의 방향을 유지한 채로 4방향으로 이동한다.

2. 현재 좌표에 있는 통나무를 회전한 상태로 다음 좌표로 이동한다.

 

통나무의 중심 좌표를 (x,y)라고 할 때 수평 방향 통나무, 수직 방향 통나무 각각을 아래와 같이 표현할 수 있습니다.

수평 방향 통나무 (x, y - 1) (x, y) (x, y + 1)
수직 방향 통나무 (x -1, y) (x, y) (x + 1, y)

 

다음 좌표로 이동할 때 위 세 좌표가 '1'을 만나는지, 배열을 벗어나는지 판단해 주면 됩니다.

회전할 때는 중심 좌표를 기준으로 8방향을 확인하면 됩니다.

 

방문 체크는 (x, y)의 좌표를 수평 또는 수직 방향으로 방문했는지 판단하기 위해서 3차원 배열을 사용합니다.

visited[x][y][k]

=> (x, y)의 좌표를 k 방향 통나무를 가지고 방문한 적이 있는가?

=> k =0 이면 수평 방향, k = 1이면 수직 방향입니다.

 

 

<C++소스코드>

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
#include <iostream>
#include <algorithm>
#include <vector>
#include <utility>
#include <queue>
using namespace std;
 
#define X first
#define Y second
 
class Bar {
public:
    int x, y, state;
    void setBar(int x, int y, int state) {
        this->= x;
        this->= y;
        this->state = state;
    }
    bool operator==(const Bar &bar) {
        if (this->== bar.x && this->== bar.y && this->state == bar.state) return true;
        else return false;
    }
};
 
Bar start, dest;
 
char board[50][50];
int visited[50][50][2]; // [i][j][0] = 수평, [i][j][1] = 수직, Bar의 가운데 좌표를 기준으로 방문체크
int n;
const int dx[4= {001-1};
const int dy[4= {1-100};
 
// Bar의 수평(0), 수직(1) 상태 리턴
int getState(vector<pair<intint > > bar) {
    if (bar[0].X - bar[1].X == 0return 0//연속된 2개의 B의 X 좌표가 같으면 수평
    else return 1// 그렇지 않으면 수직
}
 
void init() {
    for (int i = 0; i < 50; i++) {
        for (int j = 0; j < 50; j++) {
            for(int k=0; k<2; k++){
                visited[i][j][k] = -1;
            }
        }
    }
}
 
bool check(int x, int y) {
    return !(x < 0 || y < 0 || x >= n || y >= n || board[x][y] == '1');
}
 
void bfs() {
    queue<Bar> Q;
    Q.push({ start.x, start.y, start.state });
    visited[start.x][start.y][start.state] = 0;
 
    while (!Q.empty()) {
        auto cur = Q.front(); Q.pop();
        if (cur == dest) {
            return;
        }
        if (cur.state == 0) { // 수평으로 이동
            for (int dir = 0; dir < 4; dir++) { // 4방향 이동
                int nx = cur.x + dx[dir];
                int ny = cur.y + dy[dir];
                if (!check(nx, ny - 1|| !check(nx, ny) || !check(nx, ny + 1|| visited[nx][ny][cur.state] != -1continue;
                Q.push({ nx, ny, cur.state });
                visited[nx][ny][cur.state] = visited[cur.x][cur.y][cur.state] + 1;
            }
            //수직방향으로 회전
            if (!check(cur.x - 1, cur.y) || !check(cur.x, cur.y) || !check(cur.x + 1, cur.y) || visited[cur.x][cur.y][!cur.state] != -1continue;
            if (!check(cur.x - 1, cur.y - 1|| !check(cur.x - 1, cur.y + 1|| !check(cur.x + 1, cur.y - 1|| !check(cur.x + 1, cur.y + 1))continue;
            Q.push({ cur.x, cur.y, !cur.state });
            visited[cur.x][cur.y][!cur.state] = visited[cur.x][cur.y][cur.state] + 1;
 
        }
        else { // 수직으로 이동
            for (int dir = 0; dir < 4; dir++) { // 4방향 이동
                int nx = cur.x + dx[dir];
                int ny = cur.y + dy[dir];
                if (!check(nx - 1, ny) || !check(nx, ny) || !check(nx + 1, ny) || visited[nx][ny][cur.state] != -1continue;
                Q.push({ nx, ny, cur.state });
                visited[nx][ny][cur.state] = visited[cur.x][cur.y][cur.state] + 1;
            }
            //수평방향으로 회전
            if (!check(cur.x, cur.y - 1|| !check(cur.x, cur.y) || !check(cur.x, cur.y + 1|| visited[cur.x][cur.y][!cur.state] != -1continue;
            if (!check(cur.x - 1, cur.y - 1|| !check(cur.x - 1, cur.y + 1|| !check(cur.x + 1, cur.y - 1|| !check(cur.x + 1, cur.y + 1))continue;
            Q.push({ cur.x, cur.y, !cur.state });
            visited[cur.x][cur.y][!cur.state] = visited[cur.x][cur.y][cur.state] + 1;
        }
    }
}
 
void input() {
    cin >> n;
    vector<pair<intint> > S, E;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> board[i][j];
            if (board[i][j] == 'B') S.push_back({ i, j });
            else if (board[i][j] == 'E')E.push_back({ i, j });
        }
    }
    start.setBar(S[1].X, S[1].Y, getState(S));
    dest.setBar(E[1].X, E[1].Y, getState(E));
}
void solve() {
    init();
    input();
    bfs();
    int ans = visited[dest.x][dest.y][dest.state];
    if (ans == -1cout << '0';
    else cout << ans;
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
 
    solve();
 
    return 0;
}
cs
 

 

반응형
반응형

문제 링크: https://www.acmicpc.net/problem/9944

 

9944번: NxM 보드 완주하기

N×M 보드 위에서 할 수 있는 게임이 있다. 보드는 크기가 1×1인 정사각형 칸으로 나누어져 있다. 보드의 각 칸은 빈 칸 또는 장애물이다. 장애물은 아래 그림에선 어두운 사각형으로 표시되어져

www.acmicpc.net

<문제 풀이> DFS, 백트래킹

 

아래와 같은 조건으로 DFS를 구현하면 됩니다.

1. 모든 빈칸을 방문했을 때 단계의 최솟값을 업데이트한다.

2. 현재 방향을 가지고 다음 좌표로 갈 수 있으면 방향을 유지해서 다음 좌표로 이동한다.

3. 현재 방향을 가지고 다음 좌표로 갈 수 없다면 다른 3가지 방향을 선택해서 다른 좌표로 이동한다.

 

단계의 최솟값을 구하기 위해서 단계 k를 DFS의 파라미터로 전달해야 됩니다.

1. 방향을 유지해서 다음 좌표로 갈 때는 단계를 증가시키지 않는다.

2. 방향을 바꿔서 다음 좌표로 이동할 때는 단계를 1 증가시킨다. 

 

단계를 구할 때 한 가지 주의할 점은 공이 시작점에 놓였을 때 사방이 장애물이면 공은 이동하지 않기 때문에 단계는 0이 됩니다.

 

공은 이미 지나간 곳, 장애물, 보드의 경계를 만날 때까지 한 방향을 유지해서 계속 이동하기 때문에 실제 시간복잡도는 가능한 4방향을 다 해보는 것보다 훨씬 줄어듭니다.

 

 

<C++소스코드>

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
#include <iostream>
#include <algorithm>
using namespace std;
 
 
const int dx[4= { 001-1 };
const int dy[4= { 1-10 , 0 };
 
int n, m;
char board[30][30];
bool visited[30][30];
int blankCnt = 0;
int ans = 1e9;
 
void init() {
    blankCnt = 0;
    ans = 1e9;
}
 
//다음 좌표로 이동할 수 있는지 판별
bool check(int x, int y) {
    return !(x < 0 || y < 0 || x >= n || y >= m || visited[x][y] || board[x][y] == '*');
}
 
/*
x, y, dir : x,y 좌표에서 현재 dir 방향을 바라보고 있음
cnt : 현재까지 cnt개의 빈칸을 방문
k : 현재까지 총 k 단계 진행
*/
void dfs(int x, int y, int dir, int cnt, int k) {
    if (cnt == blankCnt) ans = min(ans, k); //모든 빈칸을 채웠을 경우에 최솟값 갱신
    visited[x][y] = true;
    int nx = x + dx[dir];
    int ny = y + dy[dir];
    if (check(nx, ny)) { // 현재 방향을 유지해서 다음 좌표로 갈 수 있으면
        dfs(nx, ny, dir, cnt + 1, k); // 방향을 유지해서 다음 좌표로 이동, 단계 유지
    }
    else { // 다음 좌표로 갈 수 없다면
        for (int nextDir = 0; nextDir < 4; nextDir++) {
            if (nextDir == dir) continue// 방향을 바꿔서
            nx = x + dx[nextDir];
            ny = y + dy[nextDir];
            if (check(nx, ny)) { // 다른 좌표로 갈 수 있으면
                dfs(nx, ny, nextDir, cnt + 1, k + 1); // 다음 좌표로 이동, 단계 1 증가
            }
        }
        
    }
    visited[x][y] = false;
 
}
 
void solve() {
    int test_case = 0;
    while ((cin >> n >> m)) {
        init();
        test_case++;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                cin >> board[i][j];
                if (board[i][j] == '.') blankCnt++;
            }
        }
 
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                for (int dir = 0; dir < 4; dir++) {
                    if (board[i][j] == '.' && check(i + dx[dir], j + dy[dir])) { 
                        dfs(i, j, dir, 11); // 이동 방향으로 이동 가능하면 단계 1부터 시작
                    }
                    else if (board[i][j] == '.' && !check(i + dx[dir], j + dy[dir])) {
                        dfs(i, j, dir, 10); // 제자리에만 있을 수 있으면 단계 0
                    }
                }
            }
        }
        if (ans == 1e9) {
            cout << "Case " << test_case << ": " << -1 << '\n';
        }
        else {
            cout << "Case " << test_case << ": " << ans << '\n';
        }
    }
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
 
    solve();
 
    return 0;
 
}
cs
 

 

반응형
반응형

문제 링크: https://www.acmicpc.net/problem/23290

 

23290번: 마법사 상어와 복제

첫째 줄에 물고기의 수 M, 상어가 마법을 연습한 횟수 S가 주어진다. 둘째 줄부터 M개의 줄에는 물고기의 정보 fx, fy, d가 주어진다. (fx, fy)는 물고기의 위치를 의미하고, d는 방향을 의미한다. 방향

www.acmicpc.net

<문제 풀이> 시뮬레이션, 중복 순열, DFS

 

이 문제는 실수할 여지가 많았습니다. 저는 1번에서 5번을 순서대로 함수로 구현했습니다. 다음 함수 구현으로 넘어가기 전에 함수를 먼저 테스트하고 넘어갔습니다. 함수를 만들 때마다 테스트를 했던 방식이 실수를 줄이는 데 도움이 많이 되었던 것 같습니다.

 

 

0. 물고기, 상어, 냄새 표현하기

물고기는 격자에 여러 마리가 존재할 수 있기 때문에 vector형 2차원 배열을 만들었습니다.

 

상어는 한 마리만 존재할 수 있기 때문에 bool형 2차원 배열을 만들었습니다.

 

격자에 생성된 냄새가 몇 번째 연습 때 생성된 냄새인지를 표현하기 위해서 int형 2차원 배열을 만들었습니다.

 

vector<int> fish[5][5];
bool shark[5][5];
int smell[5][5]; //몇 번째 연습 때 생성된 냄새인지 저장

 

 

 

1. 모든 물고기를 복제한다.  5번에서 실제 복제가 된다.

이 부분은 간단히 연습이 시작될 때마다 격자판에 있는 물고기들을 백업해 두면 됩니다.

//1. 모든 물고기를 복제한다. 5번에서 실제 복제가 된다.
void backupFish() {
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			clonedFish[i][j] = fish[i][j];
		}
	}
}

2. 모든 물고기가 한 칸 이동한다.

물고기가 이동한 정보를 배열에 반영할 때 바로 반영하면 물고기가 두 번 이상 움직일 수도 있습니다. 현재 바라보는 격자 칸이 (x, y) 일 때 해당 격자에서 이동한 물고기가 (x2,y2)로 이동했다고 하면, 다음 바라보는 격자가 (x2,y2) 일 때 이동했던 물고기가 한 번 더 이동하게 되기 때문입니다.

 

따라서 임시 배열에 물고기의 이동 정보들을 저장해두고 모든 물고기가 이동을 완료했으면 그때 원본 배열에 다시 저장해 두었습니다.

 

반시계 방향 전환은 단순히 방향값을 1씩 감소하면 되고, 최대 7번까지 방향 전환을 해보면 됩니다.

 

const int fishDx[9] = {0, 0, -1, -1, -1, 0, 1, 1, 1};  // 물고기 방향 1 ~ 8
const int fishDy[9] = {0, -1, -1, 0, 1, 1, 1, 0, -1};


vector<int> fish[5][5];
vector<int> movedFish[5][5]; // 물고기 이동 후
bool shark[5][5];
int smell[5][5]; //몇 번째 연습 때 생성된 냄새인지 저장

//2. 모든 물고기가 한 칸 이동한다.
void moveFish() {
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			for (auto dir : fish[i][j]) {
				int cnt = 8; // 최대 7번까지 반시계 방향으로 회전해볼 수 있다.
				bool moved = false;
				int tempDir = dir;
				while (cnt--) {
					int nx = i + fishDx[tempDir];
					int ny = j + fishDy[tempDir];

					// 격자 범위를 벗어나는 칸, 상어가 있는 칸, 물고기의 냄새가 있는 칸이면 이동할 수 없다.
					if (nx < 1 || ny < 1 || nx> N || ny > N || shark[nx][ny] || smell[nx][ny]) {
						tempDir--; //반시계 방향으로 회전
						if (tempDir == 0) tempDir = 8;
						continue; 
					}
					// 그 외의 경우에는 그 칸으로 이동한다.
					moved = true;
					movedFish[nx][ny].push_back(tempDir);
					break;
				}
				if (!moved) movedFish[i][j].push_back(dir); //이동할 수 있는 칸이 없으면 이동 X
				
			}
		}
	}
	// 물고기들의 이동 정보를 실제로 반영
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			fish[i][j] = movedFish[i][j];
		}
	}
}

 

3. 상어가 연속해서 3칸 이동한다.

상어의 현재 위치에서 중복 순열 DFS로 상어가 이동할 수 있는 연속된 3칸을 구하면 됩니다. 중복 순열 DFS는 사전순으로 먼저 찾기 때문에 각 경우의 수에서 물고기가 더 많아졌을 때 상어가 이동한 좌표들을 업데이트하면 됩니다.

 

상어는 [상, 하, 상]처럼 이미 방문했던 좌표라도 다시 방문할 수 있습니다. 이때 다시 방문한 곳에 있는 물고기를 중복해서 먹지 않도록 DFS의 base case(k===3) 일 때 방문체크 배열을 만들어서 중복 부분을 처리해줘야 합니다.

 

DFS로 상어가 이동할 수 있는 좌표들을 구했으면 실제로 배열에 업데이트를 해야 합니다. 이때 상어가 이동한 칸에 물고기가 존재하면 물고기 냄새를 남겨야 합니다.

물고기 냄새는 현재까지 진행한 연습 횟수를 값으로 저장했습니다. 나중에 4번에서 두 번 전 연습에서 생긴 물고기를 제거할 때 if(현재까지 진행한 연습 횟수 - smell[i][j]  == 2)가 되는 경우를 찾아 제거해 주면 됩니다.

const int sharkDx[5] = {0, -1, 0, 1, 0}; // 상어 방향 1 ~ 4
const int sharkDy[5] = {0, 0, -1, 0, 1}; 

vector<int> fish[5][5];
pair<int, int> tempMovedShark[3]; 
pair<int, int> movedShark[3]; // 상어가 이동한 좌표
int maxFishCnt = -1;
bool shark[5][5];
int smell[5][5]; //몇 번째 연습 때 생성된 냄새인지 저장
int t = 0; // 현재까지 진행한 연습 횟수

// 3. 상어가 연속해서 3칸 이동한다.
void moveSharkDFS(int x, int y, int k) {
	if (k == 3) { // 상어가 3칸 이동
		int fishCnt = 0;
		bool visited[5][5]; //상어가 이미 먹은 물고기를 중복해서 먹지 않도록 방문 체크
		fill(&visited[0][0], &visited[4][5], false);
		for (int i = 0; i < k; i++) {
			auto cur = tempMovedShark[i];
			if (!visited[cur.X][cur.Y]) {
				fishCnt += fish[cur.X][cur.Y].size();
				visited[cur.X][cur.Y] = true;
			}
		}
		// 중복 순열 DFS는 사전순으로 먼저 찾기 때문에 각 경우의수에서 물고기가 더 많아졌는지만 판단하면 된다.
		if (fishCnt > maxFishCnt) {
			maxFishCnt = fishCnt;
			for (int i = 0; i < k; i++) {
				movedShark[i] = tempMovedShark[i];
			}
		}
		return;
	}
	for (int dir = 1; dir <= 4; dir++) {
		int nx = x + sharkDx[dir];
		int ny = y + sharkDy[dir];
		if (nx < 1 || ny < 1 || nx > N || ny > N) continue; //격자의 범위를 벗어나는 칸이 있으면 이동 X
		tempMovedShark[k].X = nx;
		tempMovedShark[k].Y = ny;
		moveSharkDFS(nx, ny, k + 1);
	}
}
void moveShark() {
	for (int i = 1; i <= N; i++){
		for (int j = 1; j <= N; j++) {
			if (shark[i][j]) {
				moveSharkDFS(i, j, 0);
				shark[i][j] = false;
			}
		}
	}
	for (int i = 0; i < 3; i++) {

		if (fish[movedShark[i].X][movedShark[i].Y].size()) { // 상어가 이동한 칸에 물고기가 있으면
			fish[movedShark[i].X][movedShark[i].Y] = {}; // 그 칸에 있는 모든 물고기를 제외시키고
			smell[movedShark[i].X][movedShark[i].Y] = t; // 물고기 냄새를 남긴다.
		}
		if (i == 2) shark[movedShark[i].X][movedShark[i].Y] = true; // 상어의 최종 위치
	}

}

 

4. 두 번 전 연습에서 생긴 물고기의 냄새가 격자에서 사라진다.

상어가 (i, j)에 냄새를 남길 때 smell[i][j]의 배열에 현재까지 진행한 연습 횟수를 값으로 저장했습니다.

따라서 if(현재까지 진행한 연습 횟수 - smell[i][j] == 2) 일 때 제거해 주면 됩니다.

 

int t = 0; // 현재까지 진행한 연습 횟수
int smell[5][5]; //몇 번째 연습 때 생성된 냄새인지 저장

// 4. 두 번 전 연습에서 생긴 물고기의 냄새가 격자에서 사라진다.
void removeSmell() {
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			if (t - smell[i][j] == 2) {
				smell[i][j] = 0;
			}
		}
	}
}

 

5. 1에서 사용한 복제 마법이 완료된다.

 

1번에서 백업해 두었던 물고기를 복제해 줍니다.

 

vector<int> fish[5][5];
vector<int> clonedFish[5][5]; // 물고기 복제 후

//5. 1에서 사용한 복제 마법이 완료된다.
void cloneFish() {
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			for (auto dir : clonedFish[i][j]) {
				fish[i][j].push_back(dir);
			}
		}
	}
}

 

 

6. 1~5번에서 구현한 함수로 연습을 진행한다.

연습마다 초기화, 현재까지 진행한 연습 횟수 카운트가 필요합니다.

물고기의 개수는 vector의 사이즈와 같습니다.

 

 

// 매 연습마다 초기화
void init() {
	maxFishCnt = -1;
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			movedFish[i][j] = {};
			clonedFish[i][j] = {};
		}
	}
	for (int i = 0; i < 3; i++) {
		tempMovedShark[i] = {};
		movedShark[i] = {};
	}
}


// 물고기 개수 리턴
int getFishCnt() {
	int cnt = 0;
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			cnt += fish[i][j].size();
		}
	}
	return cnt;
}


int solve() {
	input();
	while (s--) {
		t++; //현재까지 진행한 연습 횟수 1증가
		init();
		backupFish(); // 1
		moveFish(); // 2
		moveShark(); // 3
		removeSmell(); // 4
		cloneFish(); // 5
	}
	return getFishCnt();
}

 

 

 

 

<전체 C++소스코드>

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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#include <iostream>
#include <utility>
#include <vector>
using namespace std;
 
#define N 4
#define X first
#define Y second
 
const int fishDx[9= {00-1-1-10111};  // 물고기 방향 1 ~ 8
const int fishDy[9= {0-1-101110-1};
const int sharkDx[5= {0-1010}; // 상어 방향 1 ~ 4
const int sharkDy[5= {00-101}; 
 
vector<int> fish[5][5];
vector<int> clonedFish[5][5]; // 물고기 복제 후
vector<int> movedFish[5][5]; // 물고기 이동 후
pair<intint> tempMovedShark[3]; 
pair<intint> movedShark[3]; // 상어가 이동한 좌표
int maxFishCnt = -1;
bool shark[5][5];
int smell[5][5]; //몇 번째 연습 때 생성된 냄새인지 저장
 
int m, s;
int t = 0// 현재까지 진행한 연습 횟수
 
// 매 연습마다 초기화
void init() {
    maxFishCnt = -1;
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            movedFish[i][j] = {};
            clonedFish[i][j] = {};
        }
    }
    for (int i = 0; i < 3; i++) {
        tempMovedShark[i] = {};
        movedShark[i] = {};
    }
}
 
 
//1. 모든 물고기를 복제한다. 5번에서 실제 복제가 된다.
void backupFish() {
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            clonedFish[i][j] = fish[i][j];
        }
    }
}
 
//2. 모든 물고기가 한 칸 이동한다.
void moveFish() {
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            for (auto dir : fish[i][j]) {
                int cnt = 8// 최대 7번까지 반시계 방향으로 회전해볼 수 있다.
                bool moved = false;
                int tempDir = dir;
                while (cnt--) {
                    int nx = i + fishDx[tempDir];
                    int ny = j + fishDy[tempDir];
 
                    // 격자 범위를 벗어나는 칸, 상어가 있는 칸, 물고기의 냄새가 있는 칸이면 이동할 수 없다.
                    if (nx < 1 || ny < 1 || nx> N || ny > N || shark[nx][ny] || smell[nx][ny]) {
                        tempDir--//반시계 방향으로 회전
                        if (tempDir == 0) tempDir = 8;
                        continue
                    }
                    // 그 외의 경우에는 그 칸으로 이동한다.
                    moved = true;
                    movedFish[nx][ny].push_back(tempDir);
                    break;
                }
                if (!moved) movedFish[i][j].push_back(dir); //이동할 수 있는 칸이 없으면 이동 X
                
            }
        }
    }
    // 물고기들의 이동 정보를 실제로 반영
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            fish[i][j] = movedFish[i][j];
        }
    }
}
 
 
// 3. 상어가 연속해서 3칸 이동한다.
void moveSharkDFS(int x, int y, int k) {
    if (k == 3) { // 상어가 3칸 이동
        int fishCnt = 0;
        bool visited[5][5]; //상어가 이미 먹은 물고기를 중복해서 먹지 않도록 방문 체크
        fill(&visited[0][0], &visited[4][5], false);
        for (int i = 0; i < k; i++) {
            auto cur = tempMovedShark[i];
            if (!visited[cur.X][cur.Y]) {
                fishCnt += fish[cur.X][cur.Y].size();
                visited[cur.X][cur.Y] = true;
            }
        }
        // 중복 순열 DFS는 사전순으로 먼저 찾기 때문에 각 경우의수에서 물고기가 더 많아졌는지만 판단하면 된다.
        if (fishCnt > maxFishCnt) {
            maxFishCnt = fishCnt;
            for (int i = 0; i < k; i++) {
                movedShark[i] = tempMovedShark[i];
            }
        }
        return;
    }
    for (int dir = 1; dir <= 4; dir++) {
        int nx = x + sharkDx[dir];
        int ny = y + sharkDy[dir];
        if (nx < 1 || ny < 1 || nx > N || ny > N) continue//격자의 범위를 벗어나는 칸이 있으면 이동 X
        tempMovedShark[k].X = nx;
        tempMovedShark[k].Y = ny;
        moveSharkDFS(nx, ny, k + 1);
    }
}
void moveShark() {
    for (int i = 1; i <= N; i++){
        for (int j = 1; j <= N; j++) {
            if (shark[i][j]) {
                moveSharkDFS(i, j, 0);
                shark[i][j] = false;
            }
        }
    }
    for (int i = 0; i < 3; i++) {
 
        if (fish[movedShark[i].X][movedShark[i].Y].size()) { // 상어가 이동한 칸에 물고기가 있으면
            fish[movedShark[i].X][movedShark[i].Y] = {}; // 그 칸에 있는 모든 물고기를 제외시키고
            smell[movedShark[i].X][movedShark[i].Y] = t; // 물고기 냄새를 남긴다.
        }
        if (i == 2) shark[movedShark[i].X][movedShark[i].Y] = true// 상어의 최종 위치
    }
 
}
 
// 4. 두 번 전 연습에서 생긴 물고기의 냄새가 격자에서 사라진다.
void removeSmell() {
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            if (t - smell[i][j] == 2) {
                smell[i][j] = 0;
            }
        }
    }
}
 
//5. 1에서 사용한 복제 마법이 완료된다.
void cloneFish() {
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            for (auto dir : clonedFish[i][j]) {
                fish[i][j].push_back(dir);
            }
        }
    }
}
 
 
// 물고기 개수 리턴
int getFishCnt() {
    int cnt = 0;
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            cnt += fish[i][j].size();
        }
    }
    return cnt;
}
 
void input() {
    cin >> m >> s;
    for (int i = 0; i < m; i++) {
        int fx, fy, d; cin >> fx >> fy >> d;
        fish[fx][fy].push_back(d);
    }
    int sx, sy; cin >> sx >> sy;
    shark[sx][sy] = true;
}
 
int solve() {
    input();
    while (s--) {
        t++//현재까지 진행한 연습 횟수 1증가
        init();
        backupFish(); // 1
        moveFish(); // 2
        moveShark(); // 3
        removeSmell(); // 4
        cloneFish(); // 5
    }
    return getFishCnt();
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
 
    
    cout << solve();
 
    return 0;
 
}
cs
 

 

반응형
반응형

문제 링크: https://www.acmicpc.net/problem/17135

 

17135번: 캐슬 디펜스

첫째 줄에 격자판 행의 수 N, 열의 수 M, 궁수의 공격 거리 제한 D가 주어진다. 둘째 줄부터 N개의 줄에는 격자판의 상태가 주어진다. 0은 빈 칸, 1은 적이 있는 칸이다.

www.acmicpc.net

<문제 풀이> 우선순위큐, 시뮬레이션 

 

저는 이 문제를 다음과 같은 순서로 구현했습니다.

 

1. 적들을 아래로 한 칸 이동시키기

N행을 모두 0으로 만듭니다. (성에 도달한 적은 게임에서 제외)
N-1행부터 1행까지 순회하면서 i행에 있는 모든 원소를 i + 1행으로 옮긴다.

 

void moveEnemy() {
	for (int c = 1; c <= m; c++) { // N행에 있는 적들은 게임에서 제외
		board[n][c] = 0;
	}
	for (int r = n - 1; r >= 1; r--) { // N-1행 ~ 1행에 있는 모든 적을
		int nr = r + 1;
		for (int c = 1; c <= m; c++) {
			board[nr][c] = board[r][c]; // 아래로 한 칸 이동한다.
			board[r][c] = 0;
		}
	}
}

2. 궁수 3명을 N + 1 행에 배치하는 조합을 DFS로 구현

기본적인 조합을 구하는 DFS로 N + 1행에 궁수들을 3명 배치하면 됩니다.
궁수가 3명이 배치되면, 그때 모든 적이 격자판에서 제외될 때까지 공격과 적 이동을 반복하면 됩니다.

궁수가 3명이 배치될 때마다 새로운 게임이 진행되므로 초기 입력받은 격자판으로 복원하는 부분도 필요합니다. 

 

void recovery() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			board[i][j] = temp[i][j];
		}
	}
}

//궁수 3명을 배치하는 조합 + 게임 진행
void dfs(int idx, int k) {
	if (k == 3) { // 궁수 3명 배치 완료
		recovery(); // 매 조합마다 새로운 게임을 진행하기 위해 격자판 복구
		int capturedEnemy = 0; //잡힌 적의 수
		while (getEnemyCnt()) { // 모든 적이 격자판에서 제외될 때까지 반복
		    capturedEnemy +=  attack(); // 공격
			moveEnemy(); // 적 이동
		}
		ans = max(ans, capturedEnemy);
	}
	for (int c = idx + 1; c <= m; c++) {
		board[n + 1][c] = 1;
		dfs(c, k + 1);
		board[n + 1][c] = 0;
	}
}

 

3. 격자판에 존재하는 모든 적 구하기.
이 부분은 2차원 배열을 순회하면서 적들의 수를 세어주고 그 수를 리턴하는 함수를 작성하면 됩니다.

 

//격자판의 적의 수 리턴
int getEnemyCnt() {
	int cnt = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if(board[i][j]) cnt++;
		}
	}
	return cnt;
}

 

4. 궁수들의 적 공격 구현

 

모든 궁수는 동시에 공격을 합니다. 궁수가 공격하는 조건은 다음과 같습니다.

1) 궁수와의 거리가 D이하인 적 중에서 가장 가까운 적

2) 그러한 적이 여럿일 경우에는 가장 왼쪽에 있는 적

위 조건을 만족하는 적의 좌표 값을 r, c라고 할 때 board[r][c]의 값을 0으로 만들면 적을 제거할 수 있습니다.

위 조건을 만족하는 적의 좌표 값을 O(lgN)에 알아내는 방법은 우선순위큐를 사용하는 것입니다.

 

다음과 같이 적의 위치와, 궁수와 떨어진 거리를 멤버변수로 가지고 있는 클래스를 작성한 뒤, 비교함수를 작성하면 됩니다.

class Enemy {
public:
	int x, y; // 적의 위치
	int dist; // 궁수와 떨어진 거리
	Enemy(int x, int y, int dist) {
		this->x = x;
		this->y = y;
		this->dist = dist;
	}
};

// 가장 가까운 적이고, 가장 왼쪽에 있는 적을 공격
struct cmp {
	bool operator()(const Enemy &a, const Enemy &b){
		if (a.dist == b.dist) return a.y > b.y;
		else return a.dist > b.dist;
	}
};

 

이제 우선순위큐 3개를 선언하고( 배열 -> pq[3]) 각각의 우선순위 큐에 1번 궁수, 2번 궁수, 3번 궁수의 공격 사정거리 안에 들어오는 적들을 큐에 삽입하면 됩니다.

 

적들은 우선순위 큐의 top()에 있습니다.

 

 

 

적들을 실제로 제거할 때는 궁수 3명의 우선순위큐에 적들을 삽입하는 과정이 끝나고 난 뒤에 제거해야 합니다.

모든 궁수는 동시에 공격할 수 있기 때문입니다.

또한 사정거리 안에 없는 적들도 있을 수 있기 때문에 우선순위큐가 비어있는지도 확인해야 합니다.

 

//궁수가 공격한 적의 수를 리턴
int attack() {
	priority_queue<Enemy, vector<Enemy>, cmp> pq[3]; //각 궁수가 공격할 수 있는 적을 원소로 가지고 있음
	vector<pair<int, int> > archers; //각 궁수의 위치
	for (int i = 1; i <= m; i++) {
		if (board[n + 1][i]) archers.push_back({ n + 1, i });
	}
	for (int k = 0; k < 3; k++) { //각각의 궁수에 대해서
		for (int r = 1; r <= n; r++) {
			for (int c = 1; c <= m; c++) {
				int dist = abs(r - archers[k].R) + abs(c - archers[k].C);
				if (board[r][c] && dist <= d) { //사정거리 안에 있는 적을
					pq[k].push({ r, c, dist}); //우선순위 큐에 삽입
				}
			}
		}
	}
	int cnt = 0;
	for (int k = 0; k < 3; k++) {//각각의 궁수에 대해서
		if (pq[k].empty()) continue;  //사정거리 안에 적이 없으면 무시
		auto enemy = pq[k].top();
		if (board[enemy.x][enemy.y]) { // 사정거리 안에 있는 적 중에서 가장 가까우며, 가장 왼쪽에 있는 적 하나를 공격
			board[enemy.x][enemy.y] = 0;
			cnt++;
		}
	}
	return  cnt;
}

 

 

<전체 C++소스코드>

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
#include <iostream>
#include <algorithm>
#include <vector>
#include <utility>
#include <queue>
using namespace std;
 
#define R first
#define C second
 
int n, m, d;
int board[17][17];
int temp[17][17];
int ans = 0;
 
class Enemy {
public:
    int x, y; // 적의 위치
    int dist; // 궁수와 떨어진 거리
    Enemy(int x, int y, int dist) {
        this->= x;
        this->= y;
        this->dist = dist;
    }
};
 
void recovery() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            board[i][j] = temp[i][j];
        }
    }
}
 
// 가장 가까운 적이고, 가장 왼쪽에 있는 적을 공격
struct cmp {
    bool operator()(const Enemy &a, const Enemy &b){
        if (a.dist == b.dist) return a.y > b.y;
        else return a.dist > b.dist;
    }
};
 
 
void moveEnemy() {
    for (int c = 1; c <= m; c++) { // N행에 있는 적들은 게임에서 제외
        board[n][c] = 0;
    }
    for (int r = n - 1; r >= 1; r--) { // N-1행 ~ 1행에 있는 모든 적을
        int nr = r + 1;
        for (int c = 1; c <= m; c++) {
            board[nr][c] = board[r][c]; // 아래로 한 칸 이동한다.
            board[r][c] = 0;
        }
    }
}
 
//궁수가 공격한 적의 수를 리턴
int attack() {
    priority_queue<Enemy, vector<Enemy>, cmp> pq[3]; //각 궁수가 공격할 수 있는 적을 원소로 가지고 있음
    vector<pair<intint> > archers; //각 궁수의 위치
    for (int i = 1; i <= m; i++) {
        if (board[n + 1][i]) archers.push_back({ n + 1, i });
    }
    for (int k = 0; k < 3; k++) { //각각의 궁수에 대해서
        for (int r = 1; r <= n; r++) {
            for (int c = 1; c <= m; c++) {
                int dist = abs(r - archers[k].R) + abs(c - archers[k].C);
                if (board[r][c] && dist <= d) { //사정거리 안에 있는 적을
                    pq[k].push({ r, c, dist}); //우선순위 큐에 삽입
                }
            }
        }
    }
    int cnt = 0;
    for (int k = 0; k < 3; k++) {//각각의 궁수에 대해서
        if (pq[k].empty()) continue;  //사정거리 안에 적이 없으면 무시
        auto enemy = pq[k].top();
        if (board[enemy.x][enemy.y]) { // 사정거리 안에 있는 적 중에서 가장 가까우며, 가장 왼쪽에 있는 적 하나를 공격
            board[enemy.x][enemy.y] = 0;
            cnt++;
        }
    }
    return  cnt;
}
 
 
//격자판의 적의 수 리턴
int getEnemyCnt() {
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if(board[i][j]) cnt++;
        }
    }
    return cnt;
}
 
//궁수 3명을 배치하는 조합 + 게임 진행
void dfs(int idx, int k) {
    if (k == 3) { // 궁수 3명 배치 완료
        recovery(); // 매 조합마다 새로운 게임을 진행하기 위해 격자판 복구
        int capturedEnemy = 0//잡힌 적의 수
        while (getEnemyCnt()) { // 모든 적이 격자판에서 제외될 때까지 반복
            capturedEnemy +=  attack(); // 공격
            moveEnemy(); // 적 이동
        }
        ans = max(ans, capturedEnemy);
    }
    for (int c = idx + 1; c <= m; c++) {
        board[n + 1][c] = 1;
        dfs(c, k + 1);
        board[n + 1][c] = 0;
    }
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
 
    cin >> n >> m >> d;
    for (int r = 1; r <= n; r++) {
        for (int c = 1; c <= m; c++) {
            cin >> board[r][c];
            temp[r][c] = board[r][c];
        }
    }
    dfs(00);
    cout << ans;
    return 0;
 
}
cs
 
 

 

반응형
반응형

문제 링크: https://www.acmicpc.net/problem/9205

 

9205번: 맥주 마시면서 걸어가기

송도에 사는 상근이와 친구들은 송도에서 열리는 펜타포트 락 페스티벌에 가려고 한다. 올해는 맥주를 마시면서 걸어가기로 했다. 출발은 상근이네 집에서 하고, 맥주 한 박스를 들고 출발한다.

www.acmicpc.net

<문제 풀이> 그래프 이론, BFS 알고리즘

 

상근이가 50미터를 가려면 맥주를 한 병 마셔야 합니다. 맥주는 최대 20개까지 가지고 있을 수 있고 편의점에 가면 빈 맥주가 있을 때 리필할 수 있습니다.

 

상근이가 50미터마다 맥주 한 병을 마신다고 생각하지 말고 다음과 같이 바꿔서 생각하면 문제를 쉽게 풀 수 있습니다.

 

상근이는 맥주를 한 번에 다 먹는다. 그러면 상근이는 현재 위치에서 (먹은 맥주 개수 * 50) m만큼 이동할 수 있는 능력치가 생긴다.

편의점을 방문했을 때 남아 있는 이동거리가 950m(빈 병이 하나라도 남은경우)보다 작으면 이동거리가 다시 1000m가 된다.

 

이제 문제를 해결하기 위해서 상근이의집, 페스티벌, 편의점을 그래프의 정점으로 표현합니다.

그다음 상근이 집 정점에서 다른 모든 정점을 탐색하는 기본적인 BFS를 구현하면 됩니다.

이때 방문 가능한 조건은 이전에 방문한 적 없으면서 남아 있는 이동 거리로 다음 정점을 방문할 수 있어야 합니다.

 

 

 

<C++소스코드>

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
#include <iostream>
#include <vector>
#include <tuple>
#include <algorithm>
#include <cmath>
#include <queue>
using namespace std;
 
 
 
class Vertex {
public:
    int x, y;
    int rDist; // 갈 수 있는 남은 거리
    int num; // 편의점 번호
    Vertex(int x, int y, int rDist, int num) : x(x), y(y), rDist(rDist), num(num) {
    }
};
bool visited[103]; //집 1, 페스티벌 102, 편의점 2 to n
int sx, sy;
int ex, ey;
 
vector<Vertex> graph;
 
bool bfs() {
    queue<Vertex> Q;
    Q.push({ sx, sy, 20*500 }); // 처음에 최대 갈 수 있는 거리 1000m 
    visited[0= true;
    while (!Q.empty()) {
        auto cur = Q.front(); Q.pop();
        for (auto next : graph) { // 모든 정점 확인
            if (visited[next.num])continue// 이미 방문한 정점은 무시
            int dist = abs(cur.x - next.x) + abs(cur.y - next.y); // 다음 정점까지의 거리 구하기
            if (dist > cur.rDist) continue// 갈 수 있는 남은 거리로 갈 수 없다면 무시
            int rDist = dist - cur.rDist; // 이동할 거리만큼 차감
            if (rDist <= 950) rDist = 1000// 정점은 편의점이거나 페스티벌이므로 빈 병이 있으면 무조건 가득 채우면 됨
            Q.push({ next.x, next.y, rDist, next.num }); // 다음 정점으로 이동
            visited[next.num] = true;
            if (next.x == ex && next.y == ey) return true//만약 페스티벌이면 종료
        }
    }
    return false// 페스티벌을 갈 수 없는 경우
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
 
    int t; cin >> t;
    while (t--) {
        graph.clear();
        fill(visited, visited + 103false);
        int n; cin >> n;
        cin >> sx >> sy;
        for (int i = 1; i <= n; i++) {
            int x, y; cin >> x >> y;
            graph.push_back({ x, y, 0, i });
 
        }
        cin >> ex >> ey;
        graph.push_back({ ex, ey, 0102 });
        if (bfs()) cout << "happy\n";
        else cout << "sad\n";
    }
 
    
    return 0;
 
}
cs
 

 

반응형
반응형

문제 링크: https://www.acmicpc.net/problem/1743

 

1743번: 음식물 피하기

첫째 줄에 통로의 세로 길이 N(1 ≤ N ≤ 100)과 가로 길이 M(1 ≤ M ≤ 100) 그리고 음식물 쓰레기의 개수 K(1 ≤ K ≤ N×M)이 주어진다.  그리고 다음 K개의 줄에 음식물이 떨어진 좌표 (r, c)가 주어진다

www.acmicpc.net

<문제 풀이> BFS 알고리즘

 

먼저 2차원 배열을 선언하고, 음식물이 있는 곳을 1로 표현하고 음식물이 없는 곳을 0으로 표현합니다.

 

그다음 (r,c)에 있는 음식물에 대해서 인접한 음식물의 크기를 구할 수 있는 BFS를 구현합니다.

크기를 구하는 방법은 기본적인 BFS 구현에서 큐에 탐색할 next 좌표를 넣을 때 크기를 1 증가시키는 코드만 추가하면 됩니다.

 

구현한 BFS를 모든 음식물 각각에 대해서 호출하면 되는데, 이전에 BFS로 방문했던 곳에 대해서는 BFS를 호출하지 않도록 해야 합니다.

 

 

 

<C++소스코드>

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
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
 
#define X first
#define Y second
 
int n, m, k;
 
bool board[101][101];
bool visited[101][101];
const int dx[4= { 0,01-1 };
const int dy[4= { 1-100 };
 
int bfs(int x, int y) {
    int size = 1;
    queue<pair<intint> > Q;
    Q.push({ x, y });
    visited[x][y] = true;
 
    while (!Q.empty()) {
        auto cur = Q.front(); Q.pop();
        for (int dir = 0; dir < 4; dir++) {
            int nx = cur.X + dx[dir];
            int ny = cur.Y + dy[dir];
            if (nx < 1 || ny <1 || nx > n || ny > m) continue;
            if (!board[nx][ny] || visited[nx][ny]) continue;
            Q.push({ nx, ny });
            visited[nx][ny] = true;
            size++;
        }
    }
    return size;
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
 
    cin >> n >> m >> k;
    while (k--) {
        int r, c; cin >> r >> c;
        board[r][c] = true;
    }
 
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (!board[i][j] || visited[i][j]) continue;
            ans = max(ans, bfs(i, j));
        }
    }
    cout << ans;
    return 0;
 
}
cs
 

 

반응형

+ Recent posts