BOJ 4217

신성문자

문제출처

문제 해석

  • HxW의 배열에서 알맞은 신성문자를 구분하고 오름차순으로 출력하는 문제입니다.
  • 입력으로 주어지는 그림은 다음의 규칙들을 만족합니다.
    1. 이미지는 주어진 6개의 신성문자만 포함합니다.
    2. 모든 이미지는 적어도 1개의 올바른 신성문자를 포함합니다.
    3. 모든 검정 픽셀은 신성문자의 일부입니다.
    4. 신성문자는 검정픽셀이 모두 이어져 있습니다.
    5. 서로 다른 문자끼리 접하지 않으며, 한 문자 안에 다른 문자가 포함되지 않습니다.
    6. 신성문자의 각도와 크기에 제한이 없습니다. 모양은 일정합니다.

설계(30)

  • 문자를 구분할 수 있는 방법을 생각하는데 오래걸렸습니다.
  • 자세히 보면 각 문자의 안에 포함된 빈공간의 개수가 모두 다른 것을 알 수 있습니다.
  • 이 아이디어와 dfs탐색을 이용해서 알맞은 문자를 찾을 수 있습니다.
  • 우선 16진수로 표현되어있는 맵을 2진수로 변환합니다.
  • 문자를 찾기 전에 배경을 모두 방문 표시해 놓습니다(실수요소)
  • 그 후, 방문하지 않은 검정픽셀을 탐색합니다.
  • dfs를 이용하여 탐색하는 도중에 하얀색 픽셀을 만나면, 이는 빈 공간을 의미하므로, 해당 픽셀과 인접한 모든 하얀색 픽셀을 모두 방문표시 해주고 카운트를 합니다.
  • 모든 dfs를 돌았을 때, 빈공간의 개수를 알 수 있고 이에 맞는 문자를 저장해줍니다.
  • 모든 공간을 탐색한 후, 저장된 문자들을 오름차순 정렬한 후 출력합니다.

구현(40)

코드
#include <iostream>
#include <algorithm>
#include <cstring>

#define MAXN 200
using namespace std;

int map[MAXN][MAXN];
char arr[MAXN][MAXN];
bool visited[MAXN][MAXN];
int N,M;
int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};
int cnt = 0 ;
char word[] = {'W','A','K','J','S','D'};
int time;
bool is_range(int x, int y){
    return (x>=0 && y >=0 && x < N && y <4*M);
}
void check_background(int x, int y){
    visited[x][y] = true;
    for (int k = 0 ;k<4; k++){
        int nx = x + dx[k];
        int ny = y + dy[k];
        if (!is_range(nx,ny)) continue;
        if (!map[nx][ny] && !visited[nx][ny]){
            check_background(nx,ny);
        }
    }
}
void dfs(int x, int y, int mode){
    visited[x][y] = true;
    if (mode){
        for (int k = 0;k<4;k++){
            int nx = x + dx[k];
            int ny = y + dy[k];
            if (!is_range(nx,ny)) continue;
            if (visited[nx][ny] || map[nx][ny]) continue;
            dfs(nx,ny,mode);
        }
    }
    else{
        for (int k = 0 ;k< 4; k++){
            int nx = x + dx[k];
            int ny = y + dy[k];
            if (!is_range(nx,ny)) continue;
            if (visited[nx][ny]) continue;
            if (map[nx][ny]){
                dfs(nx,ny,mode);
            }
            else{
                cnt++;
                dfs(nx,ny,1);
            }
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    while (1){
        time++;
        cin >> N >> M ;
        if (N == 0 && M == 0) break;
        memset(visited,false,sizeof(visited));
        for (int i = 0 ; i <N;i++){
            for (int j = 0; j<M;j++){
                char tmp;
                cin >> tmp;
                int num;
                    if (tmp>='a'&& tmp<='f') num = tmp -'a'+10;
                    else num = tmp - '0';
                for (int k = 3; k>=0; k--){
                    map[i][4*j+k] = num%2;
                    num /= 2;
                }
            }
        }
        for (int i = 0 ; i < N; i++){
            if (!visited[i][0] && !map[i][0]){
                check_background(i,0);
            }
            if (!visited[i][4*M-1] && !map[i][4*M-1]) check_background(i,4*M-1);
        }
        for (int i = 0 ; i < 4*M; i++){
            if (!visited[0][i] && !map[0][i]){
                check_background(0,i);
            }
            if (!visited[N-1][i] && !map[N-1][i]){
                check_background(N-1,i);
            }
        }
        string ans = "";
        for (int i = 0 ; i <N ;i++){
            for (int j = 0;j<4*M; j++){
                if (!visited[i][j] && map[i][j]){
                    cnt = 0;
                    dfs(i,j,0);
                    ans += word[cnt];
                }
            }
        }
        sort(ans.begin(),ans.end());
        cout << "Case "<< time <<": " << ans <<"\n";
    }
    return 0;
}

디버깅
  • 구현할 때, 실수한 요소가 있어서 ICPC 2011에서 제공하는 테스트 셋으로 디버깅을 진행하였습니다.
  • 배경을 체크할 때, (0,0)을 기준으로 dfs탐색을 하였으나 배경색이 끊기는 경우가 생기는 반례를 보았습니다.
  • 이를 보완하기 위해서 모든 가장자리를 dfs탐색하도록 변경하였습니다.
제출결과
  • 12ms

마무리

  • 아이디어를 생각하기 어려운 그래프 이론 문제였습니다. 아이디어만 알맞게 생각하면 어렵지 않게 구현할 수 있는 문제인 것 같습니다.