[백준] 1941번 소문난 칠공주[c++]

백준 문제 링크 : 1941번: 소문난 칠공주 (acmicpc.net)

백준 1759번 암호만들기 문제 사진

백준 1941번 문제 캡쳐

문제 요약

입력 값 : S-Y(5X5 교실의 파벌 배치도)

  1. 7명의 그룹을 생성한다.
  2. 반드시 이다솜파로만 이루어져 있지 않아도 된다.
  3. 이 그룹에는 최소 이다솜파가 4명 이상 있어야 한다.
  4. 각각의 그룹원들은 세로 혹은 가로로 이어져 있어야 한다.

문제 풀이 과정

이 문제를 요약하면 교실 내 학생들을 이용하여 7명의 그룹을 구성하는데 이때 이다솜파가 4명 이상이도록 구성하고 자리가 연결되어 있도록 구성하는 것으로 요약됩니다. 언뜻 보면 간단해 보이면서도 여러 조건들을 고려하여 풀어야 하는 문제입니다.

(0, 0)(1, 0)(2, 0)(3, 0)(4, 0)
(0, 1)(1, 1)(2, 1)(2, 1)(4, 1)
(0, 2)(1, 2)(2, 2)(2, 2)(4, 2)
(0, 3)(1, 3)(2, 3)(2, 3)(4, 3)
(0, 4)(1, 4)(2, 4)(2, 4)(4, 4)
학생 배치도

가장 먼저 고려해야 할 내용은 어떻게 학생 그룹을 만드는가 입니다. 학생들의 배치는 위와 같고 해당 배치에서 학생들을 골랐을 때 이어져 있어야 하죠. 언뜻 이것만 보면 bfs나 dfs로 풀이해야 할 것처럼 보입니다. 다만 해당 방법을 이용할 경우 그룹이 한 줄로만 형성되는 문제와 중복 처리에 관한 문제가 발생합니다. 백준 문제 페이지 주어진 예제를 가져와 보겠습니다.

예제CASE 01 CASE 02
YYYYY
SYSYS
YYYYY
YSYYS
YYYYY
00000
SYSYS
0000Y
0000S
00000
00000
SYSYS
0Y000
0S000
00000
(0 = 그룹 아님, else = 그룹원)

백준 예제 입력을 보면 가능한 가짓수는 총 2개입니다. 한 줄로 형성되는 첫번째, 가지가 생기는 두번째가 있죠.

만약 bfs나 dfs로 하면 그룹이 한 줄로 형성되기 때문에 형성할 수 있는 가짓수가 제한됩니다. 때문에 다른 그룹 형성 방식으로 접근해야 합니다.

두번째로 고려해야 하는 내용은 학생을 뽑아 그룹을 만들었는데 이전에 뽑은 학생 그룹과 중복되는 경우입니다. bfs나 dfs로 이를 처리할 경우 생성한 그룹을 배열에 저장하고 그룹을 생성할 때마다 이미 생성된 그룹 배열과 확인하는 복잡한 과정을 거치게 됩니다. 때문에 이를 처리할 방법이 필요합니다.

다행히 입력되는 총 값은 25개로 동일하고 그 수가 그리 크지 않습니다. 이번 문제에서 생성 가능한 중복 없는 순서 상관없는 조합(그룹)25C7 ( 25P7 / 7!)이며 이 가짓수를 연산하는데 필요한 시간이 주어진 시간보다 적기 때문에 백트래킹을 활용한 조합 생성으로 처리가 가능합니다.

25C7 ( 25P7 / 7!) = [25 × 24 × 23 × 22 × 21 × 20 × 19 ÷ 7 ÷ 6 ÷ 5 ÷ 4 ÷ 3 ÷ 2 ÷ 1 = 480700 총 48만 7백가지]

또한 이다솜파가 최소 4명 이상이어야 한다는 조건 덕분에 중간에 가지치기를 진행하기도 용이합니다. 실제로 연산하는 횟수는 더 적겠죠.

이제 그룹이 생성되었으니 생성된 그룹이 연결되어있는지 확인하면 됩니다. 저희가 생성한 그룹은 자리가 붙어 있는지 상관없이 구성한 것이기 때문에 붙어있는 지를 선별해야해요.

이다솜파가 최소 4명 이상인 그룹들의 자리가 붙어있는지는 bfs로 판단합니다. 그룹원의 첫번째 인원을 첫 노드로 잡고 그 노드 주변에 1거리만큼 떨어진 노드를 그룹원 중 탐색합니다.

이렇게 탐색했을 때 모든 그룹원이 방문 가능하다면 그 그룹은 이어져 있다는 것이겠죠? 이러한 원리로 검증을 진행하고 만약 검증 되었다면 결과 값을 1 증가시켜주면 됩니다.

저는 조합을 생성할 때 각각의 학생이 학번을 가지고 있고 그 학번대로 자리에 앉는다고 가정하였습니다. 0-24로 학생에게 번호를 매기고 학생 자리를 찾을 때에는 (x = 학번%5, y = 학번/5)로 생각하면서 풀었죠.

아래는 문제 코드 입니다. 이번 문제는 상당히 까다로워서 주석을 부분 부분 많이 적어 두었습니다. 도움이 되셨으면 합니다.

#include <iostream>
#include <queue>
using namespace std;
//방향
pair<int, int> direction[4] = {
    {0 , 1},
    {1,0},
    {0,-1},
    {-1,0},
};
//교실
//TRUE = 이다솜 FALSE = 임도연
bool map[5][5] = {};
//교실에서 선택되었는지 학번 중복 체크
bool is_selected[25] = {};
//그룹원 위치
pair<int,int> group[7];
//그룹원 방문 판별 
bool is_group_visited[7];
//결과값
int result = 0;
//생성된 그룹이 적절한지 판별하는 함수
bool check_group(){
    //그룹 방문 배열 초기화
    fill_n(is_group_visited,7,false);
    //큐 생성
    queue<int> queue;
    //첫 노드 방문 처리
    is_group_visited[0] = true;
    //그룹 위치 중 첫노드를 큐에 기입
    queue.push(0);
    while (!queue.empty()) {
        //현재 그룹원 가져오기
        int cur_mem = queue.front();
        queue.pop();
        //가져온 그룹원 위치 저장
        int cur_x = group[cur_mem].first;
        int cur_y = group[cur_mem].second;
        for (int i = 0; i < 4; i++) {
            //4 방향으로 탐색
            int next_x = cur_x + direction[i].first;
            int next_y = cur_y + direction[i].second;
            //값을 벗어났는지 확인
            if (next_x >=5 || next_x < 0 || next_y < 0 || next_y >= 5) continue;
            //방문 가능한 노드 확인
            for(int j = 0;j < 7;j++){
                if (!is_group_visited[j]&&group[j].first == next_x && group[j].second == next_y) {
                    is_group_visited[j] = true;
                    queue.push(j);
                }
            }   
        }
    }
    //방문하지 않은 노드가 있다면 false
    for (int i = 0; i < 7; i++) {
        if (!is_group_visited[i]) {
            return false;
        }
    }
    return true;
}
//그룹 생성
void backtracking(int num,int group_count,int doyeon){
    //임도연 파가 4명을 넘으면 당연히 이다솜 파는 최솟값을 맞출 수 없음
    if (doyeon >= 4) {
        return;
    }
    //그룹이 구성되었는지 확인
    if (group_count == 7) {
        //그룹 검증 후 결과 값 증가
        if (check_group()) result++;
        return;
    }
    //그룹을 중복 없는 순서 상관 없는 조합으로 생성
    //중복이 제거되어야 하기 때문에 현재 학번 이후부터 탐색해야 함
    for (int i = num; i < 25; i++) {
        int x = i%5;
        int y = i/5;
        if (is_selected[i]) continue;
        //방문 처리
        is_selected[i] = true;
        group[group_count] = pair<int,int>(x,y);
        //그룹원 기입 후 백트래킹
        backtracking(i,group_count+1,map[x][y]?doyeon:doyeon+1);
        //방문 안함 처리
        is_selected[i] = false;
    }
}

int main()
{
    for (int i = 0;i<5;i++) {
        for (int j = 0; j < 5;j++) {
            char c;
            cin >> c;
            map[i][j] = c == 'S';
        }
    }
    backtracking(0,0,0);
    cout << result << endl;
    return 0;
}

이 문제와 비슷하지만 조합 방식이 다른 문제가 있습니다. 혹시 궁금하시다면 아래 링크를 확인해 주세요.

[백준] 1759번 암호만들기[c++] – PostiveGround

이번에 풀이한 문제 대비 쉬운 문제이고 프로그래밍에서의 조합을 구성하는 방법에 배울 때 도움이 될 것 같습니다.

백트래킹 알고리즘을 사용한 다른 문제의 경우 아래 카테고리에서 찾아보실 수 있습니다.

백트래킹 – PostiveGround

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤