https://www.acmicpc.net/problem/2931
2931번: 가스관
www.acmicpc.net
[문제 요약]
- 러시아 가스를 크로아티아로 운반하기 위해 자그레브와 모스코바는 파이프라인을 디자인하고 있다. 두 사람은 실제 디자인을 하기 전에 파이프 매니아 게임을 이용해서 설계를 해보려고 한다.
- 이 게임에서 유럽은 R행 C열로 나누어져 있다. 각 칸은 비어있거나, 아래 그림과 같은 일곱가지 기본 블록으로 이루어져 있다.
- 가스는 모스크바에서 자그레브로 흐른다. 가스는 블록을 통해 양방향으로 흐를 수 있다. '+'는 특별한 블록으로, 아래 예시처럼 두 방향 (수직, 수평)으로 흘러야 한다.
- 파이프 라인의 설계를 마친 후 두 사람은 잠시 저녁을 먹으러 갔다. 그 사이 해커가 침임해 블록 하나를 지웠다. 지운 블록은 빈 칸이 되어있다.
- 해커가 어떤 칸을 지웠고, 그 칸에는 원래 어떤 블록이 있었는지 구하는 프로그램을 작성하시오.
[문제 조건]
- 첫째 줄에 유럽의 크기 R과 C가 주어진다. (1 ≤ R, C ≤ 25)
- 다음 R개 줄에는 C개 글자가 주어지며, 다음과 같은 글자로 이루어져 있다.
- 빈칸을 나타내는 '.'
- 블록을 나타내는 '|'(아스키 124), '-','+','1','2','3','4'
- 모스크바의 위치를 나타내는 'M'과 자그레브를 나타내는 'Z'. 두 글자는 한 번만 주어진다.
- 항상 답이 존재하고, 가스의 흐름이 유일한 경우만 입력으로 주어진다.
- 또, 모스크바와 자그레브가 하나의 블록과 인접해 있는 입력만 주어진다.
- 또, 불필요한 블록이 존재하지 않는다. 즉, 없어진 블록을 추가하면, 모든 블록에 가스가 흐르게 된다.
- 지워진 블록의 행과 열 위치를 출력하고, 어떤 블록이었는지를 출력한다.
- 시간 제한 : 1초
- 메모리 제한 128MB
[문제 풀이]
이번 문제는 열심히 구현만 하면 되는 간단한(?) 구현,시뮬레이션 문제다.
일단 조건을 잘보면 시작지점과 도착지점은 무조건 하나의 블록만 인접해있기 때문에 시작지점에서 출발할 방향을 쉽게 찾을 수 있다. 따라서 처음 방향을 찾고 블록들의 조건에 맞게 쭉 이동해주면서 빈 곳을 찾은 다음 빈 곳에 알맞은 블록을 집어넣고 도착지점까지 도착할 수 있는지 찾아주면 되는 문제다.
다시 차근차근 정리하면서 구현해야될 것들을 생각해보자.
- 문제에서 없앤 블록의 위치를 찾는다.
- 없앤 블록 위치에 들어갈 수 있는 블록들의 정보를 possible_block 배열에 저장해둔다.
- possible_block을 순차적으로 확인하면서 없앤 위치에 대입해보고 도착지점까지 갈 수 있는지 시뮬레이션을 돌려 본다.
없앤 블록 위치 찾기
시작지점에서 출발하여 " . " 을 만날 때 까지 가스관을 따라 이동하면 된다.
void find_blank(pair<pii, int> pos)
{
//시작지점의 경우
if (pos.second == -1)
{
pair<pii, int> new_pos;
for (int i = 0; i < 4; i++)
{
int nx = pos.first.second + dx[i];
int ny = pos.first.first + dy[i];
if (nx <= 0 || ny <= 0 || nx >= C+1 || ny >= R+1) continue;
if (mp[ny][nx] != '.' && mp[ny][nx] != 'Z') { new_pos = { {ny,nx}, i }; break; }
}
start_dir = new_pos.second;
find_blank(new_pos);
}
else
{
//없앤 블록 위치를 찾은 경우
if (mp[pos.first.first][pos.first.second] == '.')
{
blank = pos;
return;
}
else
{
int next_dir = change_dir(pos);
int nx = pos.first.second + dx[next_dir];
int ny = pos.first.first + dy[next_dir];
pair<pii, int> tmp = { {ny,nx},next_dir };
find_blank(tmp);
}
}
}
필자는 재귀를 이용하여 위치를 구하였다.
없앤 블록 위치에 들어갈 수 있는 블록 구하기
가능한 블록을 찾는건 매우 간단하다.
앞서 우리가 없앤 블록의 위치를 구할때 사용한 변수 blank에는 위치 뿐만 아니라 진입할때의 방향이 함께 저장되어 있다.
따라서 진입할때의 방향을 알면 대입 가능한 블록의 종류를 알 수 있다.
vector<char> find_possible_block(int dir)
{
vector<char> ret;
if (dir == 0) { ret.push_back('-'); ret.push_back('+'); ret.push_back('3'); ret.push_back('4');}
else if (dir == 1) { ret.push_back('|'); ret.push_back('+'); ret.push_back('3'); ret.push_back('2'); }
else if (dir == 2) { ret.push_back('-'); ret.push_back('+'); ret.push_back('1'); ret.push_back('2'); }
else if (dir == 3) { ret.push_back('|'); ret.push_back('+'); ret.push_back('4'); ret.push_back('1'); }
return ret;
}
시뮬레이션
진입한 방향에 따라 가능한 블록들의 경우의 수는 각각 모두 4가지 경우 밖에 없다.
따라서 blank변수에 없앤 블록의 위치정보가 들어있음으로 blank변수와 possible_block배열을 이용하여 모든 경우에 대해 시뮬레이션 돌려보면서 한 가지라도 시작지점 부터 도착지점까지 도달할 수 있는지 확인하면 된다.
bool simulation()
{
pii cur_pos = s;
bool flag = true;
int dir = start_dir;
while (cur_pos != e)
{
if (mp[cur_pos.first][cur_pos.second] != 'M')
{
pair<pii, int> tmp = { cur_pos, dir };
dir = change_dir(tmp);
}
cur_pos.first = cur_pos.first + dy[dir];
cur_pos.second = cur_pos.second + dx[dir];
if (cur_pos.first <= 0 || cur_pos.second <= 0 || cur_pos.first >= R + 1 || cur_pos.second >= C + 1)
{
flag = false; break;
}
if (cant_move(dir, mp[cur_pos.first][cur_pos.second]) == false) { flag = false; break; }
}
return flag;
}
cant_move 함수는 다음 지점으로 이동 가능한지 여부를 판단하는 함수다.
bool cant_move(int dir, char next)
{
if (dir == 0)
{
if (next == '|' || next == '1' || next == '2') return false;
else return true;
}
else if (dir == 1)
{
if (next == '-' || next == '1' || next == '4') return false;
else return true;
}
else if (dir == 2)
{
if (next == '|' || next == '3' || next == '4') return false;
else return true;
}
else if (dir == 3)
{
if (next == '-' || next == '2' || next == '3') return false;
else return true;
}
return true;
}
[전체 소스 코드]
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <map>
#define INF 987654321
#define mod 1000000
#define pii pair<int,int>
using namespace std;
int R, C;
int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };
vector<vector<char>> mp;
pii s, e;
pair<pii,int> blank;
int start_dir;
int pipe_cnt = 0;
int change_dir(pair<pii,int> pos)
{
int y = pos.first.first;
int x = pos.first.second;
int cur_dir = pos.second;
// | , - , + , 1 = 「, 2 = ㄴ, 3 = 」, 4 = ㄱ
if (cur_dir == 0)
{
if (mp[y][x] == '-' || mp[y][x] == '+') return 0;
else if (mp[y][x] == '3') return 3;
else if (mp[y][x] == '4') return 1;
}
else if (cur_dir == 1)
{
if (mp[y][x] == '|' || mp[y][x] == '+') return 1;
else if (mp[y][x] == '2') return 0;
else if (mp[y][x] == '3') return 2;
}
else if (cur_dir == 2)
{
if (mp[y][x] == '-' || mp[y][x] == '+') return 2;
else if (mp[y][x] == '1') return 1;
else if (mp[y][x] == '2') return 3;
}
else if (cur_dir == 3)
{
if (mp[y][x] == '|' || mp[y][x] == '+') return 3;
else if (mp[y][x] == '1') return 0;
else if (mp[y][x] == '4') return 2;
}
return 0;
}
void find_blank(pair<pii, int> pos)
{
if (pos.second == -1)
{
pair<pii, int> new_pos;
for (int i = 0; i < 4; i++)
{
int nx = pos.first.second + dx[i];
int ny = pos.first.first + dy[i];
if (nx <= 0 || ny <= 0 || nx >= C+1 || ny >= R+1) continue;
if (mp[ny][nx] != '.' && mp[ny][nx] != 'Z') { new_pos = { {ny,nx}, i }; break; }
}
start_dir = new_pos.second;
find_blank(new_pos);
}
else
{
if (mp[pos.first.first][pos.first.second] == '.')
{
blank = pos;
return;
}
else
{
int next_dir = change_dir(pos);
int nx = pos.first.second + dx[next_dir];
int ny = pos.first.first + dy[next_dir];
pair<pii, int> tmp = { {ny,nx},next_dir };
find_blank(tmp);
}
}
}
vector<char> find_possible_block(int dir)
{
vector<char> ret;
if (dir == 0) { ret.push_back('-'); ret.push_back('+'); ret.push_back('3'); ret.push_back('4');}
else if (dir == 1) { ret.push_back('|'); ret.push_back('+'); ret.push_back('3'); ret.push_back('2'); }
else if (dir == 2) { ret.push_back('-'); ret.push_back('+'); ret.push_back('1'); ret.push_back('2'); }
else if (dir == 3) { ret.push_back('|'); ret.push_back('+'); ret.push_back('4'); ret.push_back('1'); }
return ret;
}
bool cant_move(int dir, char next)
{
if (dir == 0)
{
if (next == '|' || next == '1' || next == '2') return false;
else return true;
}
else if (dir == 1)
{
if (next == '-' || next == '1' || next == '4') return false;
else return true;
}
else if (dir == 2)
{
if (next == '|' || next == '3' || next == '4') return false;
else return true;
}
else if (dir == 3)
{
if (next == '-' || next == '2' || next == '3') return false;
else return true;
}
return true;
}
bool simulation()
{
pii cur_pos = s;
bool flag = true;
int dir = start_dir;
while (cur_pos != e)
{
if (mp[cur_pos.first][cur_pos.second] != 'M')
{
pair<pii, int> tmp = { cur_pos, dir };
dir = change_dir(tmp);
}
cur_pos.first = cur_pos.first + dy[dir];
cur_pos.second = cur_pos.second + dx[dir];
if (cur_pos.first <= 0 || cur_pos.second <= 0 || cur_pos.first >= R + 1 || cur_pos.second >= C + 1)
{
flag = false; break;
}
if (cant_move(dir, mp[cur_pos.first][cur_pos.second]) == false) { flag = false; break; }
}
return flag;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> R >> C;
mp.resize(R + 1); for (int i = 0; i <= R; i++) mp[i].resize(C + 1);
for (int i = 1; i <= R; i++)
{
for (int j = 1; j <= C; j++)
{
cin >> mp[i][j];
if (mp[i][j] == 'M') s = { i,j };
else if (mp[i][j] == 'Z') e = { i,j };
}
}
pair<pii, int > tmp = { s,-1 };
find_blank(tmp);
vector<char> possible_block = find_possible_block(blank.second);
for (int i = 0; i < possible_block.size(); i++)
{
mp[blank.first.first][blank.first.second] = possible_block[i];
if (simulation())
{
cout << blank.first.first << " " << blank.first.second << " " << possible_block[i];
break;
}
mp[blank.first.first][blank.first.second] = '.';
}
return 0;
}
최근 푼 구현 문제 중에선 코드 길이가 제일 길었던 문제인거 같다..

굳
'BOJ 풀이' 카테고리의 다른 글
[BOJ/백준 15683/C++] 감시 (1) | 2023.03.14 |
---|---|
[BOJ/백준 17143/C++] 낚시왕 (4) | 2023.03.14 |
[BOJ/백준 16954/C++] 움직이는 미로 탈출 (2) | 2023.03.10 |
[BOJ/백준 1039/C++] 교환 (0) | 2023.03.08 |
[BOJ/백준 9370/C++] 미확인 도착지 (0) | 2023.03.08 |