티스토리 뷰
틀린 부분 또는 더 효율적인 접근법에 대해 피드백 주시면 감사하겠습니다!
17135 캐슬 디펜스
문제
캐슬 디펜스는 성을 향해 몰려오는 적을 잡는 턴 방식의 게임이다. 게임이 진행되는 곳은 크기가 N×M인 격자판으로 나타낼 수 있다. 격자판은 1×1 크기의 칸으로 나누어져 있고, 각 칸에 포함된 적의 수는 최대 하나이다. 격자판의 N번행의 바로 아래(N+1번 행)의 모든 칸에는 성이 있다.
성을 적에게서 지키기 위해 궁수 3명을 배치하려고 한다. 궁수는 성이 있는 칸에 배치할 수 있고, 하나의 칸에는 최대 1명의 궁수만 있을 수 있다. 각각의 턴마다 궁수는 적 하나를 공격할 수 있고, 모든 궁수는 동시에 공격한다. 궁수가 공격하는 적은 거리가 D이하인 적 중에서 가장 가까운 적이고, 그러한 적이 여럿일 경우에는 가장 왼쪽에 있는 적을 공격한다. 같은 적이 여러 궁수에게 공격당할 수 있다. 공격받은 적은 게임에서 제외된다. 궁수의 공격이 끝나면, 적이 이동한다. 적은 아래로 한 칸 이동하며, 성이 있는 칸으로 이동한 경우에는 게임에서 제외된다. 모든 적이 격자판에서 제외되면 게임이 끝난다.
게임 설명에서 보다시피 궁수를 배치한 이후의 게임 진행은 정해져있다. 따라서, 이 게임은 궁수의 위치가 중요하다. 격자판의 상태가 주어졌을 때, 궁수의 공격으로 제거할 수 있는 적의 최대 수를 계산해보자.
격자판의 두 위치 (r1, c1), (r2, c2)의 거리는 |r1-r2| + |c1-c2|이다.
입력
첫째 줄에 격자판 행의 수 N, 열의 수 M, 궁수의 공격 거리 제한 D가 주어진다. 둘째 줄부터 N개의 줄에는 격자판의 상태가 주어진다. 0은 빈 칸, 1은 적이 있는 칸이다.
- 3 ≤ N, M ≤ 15
- 1 ≤ D ≤ 10
출력
첫째 줄에 궁수의 공격으로 제거할 수 있는 적의 최대 수를 출력한다.
예제
풀이
생각
- 조건이 많다.
- 궁수 3명을 배치하는데 M개의 공간 중 3개를 배치하는 경우의 수를 순회해야 한다.
- 적과 궁수의 거리를 계산하면서, 거리가 D 이하이면서 최소인 적을 찾아내야 한다.
- 거리가 같은 적이 여럿인 경우, 가장 왼쪽의 적을 공격한다.
- 여러 궁수가 한 적을 공격할 수 있다.
- 턴마다 적은 아래로 내려온다.
- 적이 없어지면 종료한다.
- 먼저 자료구조를 무엇을 쓸까 고민하다가 굳이 맵 전체를 저장하지 말고 적 위치만 저장해서 업데이트하면 되지 않을까 생각했다. 거리를 계산한다던가, 적을 이동시킨다던가 하는 연산에서는 문제가 없었다. 하지만 적을 공격하면 적을 삭제하는 과정이 있어야 했는데, 처음에
vector
로 위치를 저장하자니 데이터를 삭제하는게 매우 귀찮다고 생각했다. - 그리고 문제는 여러 궁수가 한 적을 공격하는 경우에 현재 턴에서 앞 궁수가 내가 공격할 적을 공격했다면 현재 내가 타겟팅하고 있는 인덱스의 적은 다른 적이 되어(
vector
가 삭제되면 앞으로 당겨지니깐) 문제가 생길 것이라 생각했다.- 이 과정에서 그럼
list
로 해볼까 했는데 똑같다. 특정 위치의list
데이터가 사라지면 기존iterator
로 가르키고 있는 공간이 없어지기 때문에 접근하면 오류가 난다.
- 이 과정에서 그럼
- 아무튼 그래서 그냥 전체 맵을 저장하고 순회하기로 했다. 맵이 크지도 않고 최대 15밖에 되지 않으므로 순회해도 오래 걸리지 않을 것이라고 생각했다.
방법
먼저 m개 중 3개의 궁수 위치를 뽑는다(조합).
vector
와prev_permutation
활용- 궁수 위치 저장 ->
present
변수
궁수마다 최적의 공격대상(D이하 최소거리, 제일 왼쪽)을 찾는다.
checking
함수 ->min_enemy
변수
최적의 공격대상을 공격한다.
kill
함수 -> 맵에 적(1)을 없앤다(0)- 이번 턴의 앞 궁수가 죽였음을 확인하기 위해 최적의 공격대상이 아직 있는가를 확인하고 죽인다.
적들을 한 번 앞으로 움직인다.
move
함수
최적의 공격대상을 찾으면서 전체 적을 카운트(
enemynum
)하고, 공격하면서 죽인 적의 수(killnum
)를 확인한다. 그리고 다음 순번에 앞으로 넘어가서 맵을 넘어가버리는 없어지는 적들의 수(gonenum
)도 확인한다. 이 3개의 데이터를 활용하면 현재 맵에 적이 있는지 없는지를 확인할 수 있다. 이것으로 종료 조건을 만들어준다.위 과정
checking
,kill
,move
를 적이 없어질 때까지 반복한다.kill
을 수행하고 죽인 적의 수를 저장해놓으면, 현재 궁수위치에서 죽일 수 있는 수를 계산할 수 있다. 전체 궁수 위치들을 돌아가면서 이 값들의 최대값을 찾는다.
유의
- 오리지널 값을 유지해야하는 곳이 있다.
- 전체 맵은 궁수 위치가 바뀔 때마다 기존의 값 그대로 유지되야 한다. 고로, 오리지널 값을 유지하면서 계속
copy
해주어야 한다.
- 전체 맵은 궁수 위치가 바뀔 때마다 기존의 값 그대로 유지되야 한다. 고로, 오리지널 값을 유지하면서 계속
- 현재 위치의 궁수와 공격할 적의 위치/걸리를 다른 자료형으로 구분하여 풀었는데, 사실 이를 하나의
struct
로 풀어도 괜찮을 것 같다. 문제가 푸는데 좀 길어지다 보니깐 둘의 자료형이 헷갈렸다.
코드
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct enemy {
int x, y, dis;
};
int n, m, d;
int orip[17][17], p[17][17];
int dis;
vector<int> archer;
vector<pair<int, int>> present(3);
enemy min_enemy[3];
void checking(int& enemynum) {
enemynum = 0;
min_enemy[0] = { -1, -1, 100 };
min_enemy[1] = { -1, -1, 100 };
min_enemy[2] = { -1, -1, 100 };
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (p[i][j] == 0) continue;
++enemynum;
for (int k = 0; k < 3; ++k) {
dis = abs(present[k].first - i) + abs(present[k].second - j);
if (dis > d) continue; // 최소거리 넘으면
if (min_enemy[k].dis == dis && min_enemy[k].y < j) continue; // 가장 왼쪽값 아니면
if (min_enemy[k].dis >= dis) min_enemy[k] = { i, j, dis };
}
}
}
}
void kill(int& killnum) {
killnum = 0;
for (int i = 0; i < 3; ++i) {
if (min_enemy[i].x < 0) continue;
if (p[min_enemy[i].x][min_enemy[i].y] == 0) continue; // 앞 궁수가 미리 죽인 것
p[min_enemy[i].x][min_enemy[i].y] = 0; // kill
killnum++;
}
}
void move(int& gonenum) {
gonenum = 0;
int tmp[17] = { 0, }, gone = 0;
for (int i = 0; i < n; ++i) {
swap(tmp, p[i]);
}
for (int i = 0; i < m; ++i)
gone += p[n - 1][i];
}
int main() {
cin >> n >> m >> d;
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
cin >> orip[i][j];
archer.push_back(1);
archer.push_back(1);
archer.push_back(1);
while (archer.size() < m) {
archer.push_back(0);
}
int res = 0;
do {
copy(&orip[0][0], &orip[0][0] + (17 * 17), &p[0][0]);
for (int i = 0, ind = 0; i < m; ++i) {
if (archer[i] == 1) {
present[ind] = { n, i };
ind++;
}
}
int num = 0, enemynum, killnum, gonenum;
while (true) {
checking(enemynum);
kill(killnum);
move(gonenum);
num += killnum;
if (enemynum - (killnum + gonenum) == 0)
break;
}
res = max(res, num);
} while (prev_permutation(archer.begin(), archer.end()));
cout << res << '\n';
return 0;
}
느낀점
- 조건이 많아서 생각을 정리하는데 오래 걸렸다. 시간제한이 있었다면 사실 시간제한 안에 못풀었을 것 같다.
- 문제를 보고, 문제를 쪼갤 수 있는 능력을 길러야할 것 같다. 함수모듈(?)식으로 쪼개서 풀어낼 수 있게. 구조들을 빨리 생각해낼 수 있는 연습으로.
- 자료형에 대한 부족한 이해를 한번 더 느꼈다.
vector
,list
를 사용할까 고민했는데 이 때 데이터 삭제에 대해 하는 법과 삭제한 부분에 접근하면 어떻게 되는가를 미리 잘 알고 있었더라면 더 빨리 풀 수 있었을 것.copy
함수를 활용하면서 2차원 배열의 복사 하는법을 부족하게 알 고 있던 것을 느꼈다.
참조
'Development > Algorithm' 카테고리의 다른 글
[Algorithm] BOJ 17281 ⚾ (0) | 2021.01.23 |
---|---|
[Algorithm] BOJ 17136 색종이 붙이기 (0) | 2021.01.23 |
[Algorithm] BOJ 18808 스티커 붙이기 (0) | 2021.01.22 |
[Algorithm] BOJ 17070 파이프 옮기기 1 (0) | 2021.01.22 |
[Algorithm] BOJ 16637 괄호 추가하기 (0) | 2021.01.22 |
- Total
- Today
- Yesterday
- WebFlux
- 백준
- 쿠버네티스
- Istio
- k8s
- HTTP
- jasync
- Algorithm
- gradle
- boj
- python
- container
- 일상
- docker
- 비동기
- 하루
- Kubernetes
- tag
- Spring
- 알고리즘
- c++
- 클린 아키텍처
- Clean Architecture
- MySQL
- Log
- java
- 로그
- Spring boot
- hexagonal architecture
- Intellij
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |