BOJ
BOJ::1400 화물차
BOJ Gold 2
BOJ::1400 화물차
- Link : BOJ::1400
- Level : Gold 2
- tag : dijkstra, graph theory, implementation
시사점
- 로직은 간단하지만, 구현이 생각보다 쉽지 않은 좋은 문제입니다.
이해(x)
- A 에서 B까지 최소한의 이동으로 이동하고, 그 이동수를 출력합니다.
- 불가능한 경우, “impossible”을 출력합니다.
- ’#’ : 도로 ( 이동 가능 )
- ’.’ : 벽 ( 이동 불가능 )
- ‘0’ - ‘9’ : 신호등
- 교차로에 설치된 신호등 입니다.
- 신호등은 좌우/상하 중 하나가 초록불일때 다른 하나는 빨간불입니다.
- 좌우 신호의 길이, 상하 신호의 길이가 주어집니다.
- 또한, 최초 신호등이 어떤 방향으로 초록불을 쏘는지도 주어집니다.
설계, 손 코딩(x)
- 손으로 중심이 되는 코드를 구현합니다.
- 일반적인 bfs문제입니다.
- 하지만, 신호등 처리가 꽤나 까다롭습니다.
- 그 이유는, 다음과 같습니다.
- 신호등은 1초부터 작동을 시작합니다.
- 또한, 만약 신호가 2초동안 켜진다고 하면,
- [1,3]이 아니라, [1,2] 입니다.
- 즉, off-by-one 실수로 인해서 시간을 꽤 소모하기 좋은 문제입니다.
- 따라서, 현재 가려는 방향으로 신호등이 가장 빨리는 시간대를 반환하는 함수를 떼어내서 구현하였습니다.
시간 복잡도
공간 복잡도
손 코딩 후 문제 리뷰(x)
- 빼먹고 손 코딩 한 것이 있는지 확인합니다.
구현(x)
함수 List
업데이트 되는 변수
- 대부분의 디버깅 문제는 업데이트 되는 변수에서 발생합니다.
실제 구현
#include<bits/stdc++.h>
#define endl '\n'
#define HOR 0
#define VER 1
#define EMPTY '#'
#define WALL '.'
#define fi first
#define se second
#define pb push_abck
#define rep(i,a,b) for(int i=a;i<b;i++)
const int MAXNM = 20, INF = 987654321;
const int dx[]={0, 0, 1, -1}, dy[]={-1, 1, 0, 0};
struct traffic{bool st; int tm[2];};
struct cell{int x; int y; int tm;
bool operator<(const struct cell& t) const{
return tm > t.tm;
}
};
using namespace std;
int cntTraffic = 0;
int n, m, sx, sy, ex, ey;
char a[MAXNM][MAXNM];
vector<traffic> light;
int seen[MAXNM][MAXNM];
priority_queue<cell> pq; // time, x, y
void input(){
cntTraffic = -1;
rep(i, 0, n){
rep(j, 0, m){
cin >> a[i][j];
if(a[i][j] == 'A') sx = i, sy = j, a[i][j] = '#';
if(a[i][j] == 'B') ex = i, ey = j, a[i][j] = '#';
if(a[i][j] >='0' && a[i][j] <= '9' && cntTraffic < (a[i][j]-'0'))
cntTraffic = a[i][j] - '0';
}
}
if(cntTraffic == -1) return;
light.clear();
light = vector<traffic> ( cntTraffic+1 );
rep(i, 0, cntTraffic+1){
bool st = HOR;
int num; char ch; int tm[2];
cin >> num >> ch >> tm[0] >> tm[1];
if(ch == '|') swap(tm[0], tm[1]), st = VER;
light[i].st = st, light[i].tm[0] = tm[0], light[i].tm[1] = tm[1];
}
}
void init(){
fill(&seen[0][0], &seen[MAXNM-1][MAXNM], INF);
while(!pq.empty()) pq.pop();
}
bool over(int x, int y){return (x<0 || y<0 || x>=n || y>=m);}
void whenAble(int nth, int& ntm, int d){
int sum = light[nth].tm[0] + light[nth].tm[1];
int tmptm = (ntm-1) % sum;
bool curdir = d/2;
if(tmptm < light[nth].tm[0]){
if(curdir == light[nth].st){
// Do nothing
}else{
ntm += (light[nth].tm[0] - tmptm);
}
}else{
if(curdir == light[nth].st){
ntm += (sum-1 - tmptm) +1;
}else{
// Do nothing
}
}
}
void bfs(){
while(!pq.empty()){
int x = pq.top().x, y = pq.top().y, tm = pq.top().tm; pq.pop();
if(x == ex && y == ey){
cout << tm << endl;
return;
}
rep(i, 0, 4){
int nx = x+dx[i], ny = y+dy[i];
if(over(nx, ny) || a[nx][ny] == WALL) continue;
if(a[nx][ny] == EMPTY){
if(seen[nx][ny] > tm+1){
seen[nx][ny] = tm+1;
pq.push({nx, ny, tm+1});
}
}else{
int nth = a[nx][ny] -'0', ntm = tm+1;
whenAble(nth, ntm, i);
if(seen[nx][ny] > ntm){
seen[nx][ny] = ntm;
pq.push({nx, ny, ntm});
}
}
}
}
cout << "impossible\n";
}
void process(){
input();
init();
seen[sx][sy] = 0;
pq.push({sx, sy, 0});
bfs();
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while(true){
cin >> n >> m;
if(n == 0 && m == 0) break;
process();
}
return 0;
}
구현 후 코드리뷰 + 예제 돌려보기(x)
- 바로 예제를 넣어서 테스트 해보기 전에, 코드를 한 번 리뷰하고, 예제의 입력이 이런식으로 왔을때 전체 코드가 어떤 식으로 순차적으로 진행되는지 답은 잘 도출될 것 같은지 확인합니다.
디버깅(x)
- off-by-one 실수를 가장 많이 한 문제이고, 그것을 유도하기에 가장 좋은 문제라고 생각합니다.
- 로직을 1단위까지 정확히 손코딩하고 시작하지 않으면, 대충 감으로 하기엔 디버깅에 시간을 많이 소모합니다.