알고리즘 자체의 난이도는 높지 않지만 예외케이스와, 기저가 많아 집중하고 체계적으로 코딩해야되는 문제였습니다.
BFS,DFS 모두 사용가능하나 brute force로 흝어봐야하는 문제이기 때문에 어차피 시간복잡도가 같다면,
메모리를 더 아낄 수 있고 코드도 좀 더 깔끔한 재귀함수 DFS를 추천드립니다.
(재귀함수를 사용하면 function stack을 계속 만드는 오버헤드는 있다고 합니다.
다만 함수 내용이 짧을 수록 함수 stack 생성 오버헤드가 상대적으로 더 큰 것이지, 그외는 괜찮은 듯합니다.
DFS도 stack 자료구조를 사용하면 재귀를 사용하지 않을 수 있습니다.)
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
typedef unsigned long long ull;
typedef vector<vector<ull>> vvi;
vvi Map;
int N;
ull Max_block;
//움직임에 대한 결과 클래스
struct Res {
vvi Map;
ull mB; //Max_Block
int cnt; //블록의 개수
Res(vvi m, ull b, int c) :Map(m), mB(b), cnt(c) {}
};
// class_dir은 상하좌우 flag
// class_dir에 따라 for문 속에서 switch,if문이 계속 돌아가는 것은
// 함수가 콜된 후로 class_dir은 바뀌지 않으므로 분기예측으로 성능이 개선될 수 있다.
Res mov(vvi M,int class_dir) {
int src;
int dst;
int dir;
int cnt = 0;
ull c_m_block=0;
//우, 상 방향움직임
if (class_dir == 0 || class_dir == 2)
{
src = 0;
dst = N;
dir = 1;
}
// 좌, 하 방향
else
{
src = N-1;
dst = -1;
dir = -1;
}
bool flag = false; //한번 합쳐진 블록이라는 flag.
// class_dir에 따라 한 라인씩 순회
for (int i = 0; i < N; i++)
{
vector<ull> line;
for (int j = src; j != dst; j+=dir)
{
int tmp;
if (class_dir < 2) tmp = M[i][j];
else tmp = M[j][i];
//앞에 값과 같고, 앞에 값이 flag=flase라면 합한다.
if (tmp != 0 && !line.empty() && !flag && line.back() == tmp)
{
line[line.size() - 1] += tmp;
flag = true;
}
//해당사항 없고 빈칸이 아니라면, line에 추가.
else if (tmp != 0)
{
line.push_back(tmp);
flag = false;
}
}
//맵에 남아있는 숫자를 카운트한다.
cnt += line.size();
// 결과로 나온 line값을
// class_dir에 맞춰서 Map에 다시 써준다.
// 이때 가장 큰 block값을 저장한다.
for (int k = 0; k < line.size(); k++)
{
switch (class_dir)
{
case 0:
M[i][k] = line[k];
break;
case 1:
M[i][N - 1 - k] = line[k];
break;
case 2:
M[k][i] = line[k];
break;
case 3 :
M[N - 1 - k][i] = line[k];
break;
}
c_m_block = max(c_m_block, line[k]);
}
//나머지 뒤에는 0 삽입
for (int k = line.size(); k < N; k++)
switch (class_dir)
{
case 0:
M[i][k] = 0;
break;
case 1:
M[i][N - 1 - k] = 0;
break;
case 2:
M[k][i] =0;
break;
case 3:
M[N - 1 - k][i] = 0;
break;
}
}
//변경된 Map, 가장큰 블록 값, 남아있는 블록의 수
return Res(M,c_m_block,cnt);
}
void brute(vvi M, int deep, ull c_m_b, int c_cnt)
{
Max_block = max(Max_block, c_m_b);
if (c_cnt == 1) return; //블록이 1개밖에 안남으면 더 진행할 필요없다.
if (deep >= 5) return;
if (Max_block >= pow(c_m_b, 6 - deep)) return; //프로미싱 하지 않다면, 더이상 진행할 필요 없다.
for (int i = 0; i < 4; i++)
{
Res res = mov(M, i);
//한번에 움직임으로 나온 결과값을 다음 brute로 전달.
brute(res.Map, deep + 1, res.mB, res.cnt);
}
}
// 디버깅용 맵 확인!
void show(vvi tmp)
{
cout << endl;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
cout << tmp[i][j]<<" ";
cout << endl;
}
cout << endl;
}
int main() {
cin >> N;
Map = vvi(N, vector<ull>(N));
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
cin >> Map[i][j];
//최소개수는 1개일 수도있지만,
//2로 시작해도, brute에서 금방 1을 인식하고 반환한다.
brute(Map, 0, 2, 2);
cout << Max_block;
}
최대한 비슷하게 동작하는 case에 대해서는 코드를 재사용하려고 노력했습니다.
조금 더 코드를 최적화 시킨다면 2차원 vector를 1차원 vector로 평탄화 한다면,
2차원 vector에서 V[행][열] 순서로 정해져서 행이동과 열이동에 같은 코드를 쓸수 없던 점을
row,col 역할을 하는 변수를 사용하여 코드의 범용성을 넓힐 수 있습니다.
예로 N = 4, i = (1 ~ N-1) 이라면 Map[ start + Jump*i ]을 통해서 원하는 2차원 맵의 start지점에서
case에 따라 Jump값만 배정을 다르게 한다면, 같은 코드로도 다른 동작을 수행시킬 수 있습니다.
Jump = 4 라면 한 행씩 하로 이동 Jump = 1이라면 한 열씩 우로 이동,
Jump = -4 라면 상, Jump = -1이라면 좌로 이동합니다.
(
사실 C의 기본 2차원( ex)int arr[5][5] ) 배열은 메모리에 포인터로 접근할때는 위 방식처럼 이미 배정되어 있으나,
DFS에 사용하려면 배열의 복사 생성과, 함수가 끝날때 메모리를 해제 기능을 직접 구현해야됩니다.
그러므로, 이미 깊은복사생성자와 소멸자가 다 구현되어있는 STL의 vector 사용이 용이 합니다.
)
하지만 한 가지의 코드가 여러 케이스에서도 잘 동작하도록 예외를 고려하는 것은 쉬운일도 아니고,
코드의 의미를 파악하기도 어려우며, 실수가 생기기 쉽습니다.
생각한 알고리즘 아이디어가 명확하다면, 코드가 길어지더라도 예외케이스가 입력에 따라 커지는게 아닌 이상
몇줄 더 길게 코딩하는게 실수를 줄이고 명확할 수 있겠습니다.
'Algorithm' 카테고리의 다른 글
백준 13460번 삼성기출 구술탈출2 (0) | 2020.04.20 |
---|---|
백준 2352번 nlog(n)으로 풀기, LIS 알고리즘의 이해 (longest increasing Subsequence ) (0) | 2020.04.10 |
백준 2631 줄세우기 LIS 개념 다이나믹 프로그래밍 (0) | 2020.04.07 |
다이나믹 프로그래밍으로 최적화 하는 DFS, 백준 2253번 점프 (0) | 2020.03.28 |
백준 1525번 퍼즐 BFS 최단,최소 문제. DFS는 힘든이유.. (0) | 2020.03.22 |
댓글