[백준/boj] 2309번: 일곱 난쟁이 | C++

2024. 9. 11. 21:56·백준 문제풀이

0. 문제

https://www.acmicpc.net/problem/2309

기존에 풀어봤던 문제인데, 다시 풀어보게 되었습니다.


1. 문제풀이 핵심

  • n이 9로 10 이하이기 때문에 무지성 재귀를 사용해도 괜찮습니다. ㅎㅎ 하지만 재귀에 대한 이해를 통해 조금 더 효율적인 코드를 작성할 수 있습니다.
  • 함수 종료 조건을 잘 걸어줘야 합니다. 답을 찾고 출력한 후에도 재귀가 계속 돌아가면 불필요한 연산이 이루어집니다.

2. 문제풀이에 사용된 개념

  • 재귀를 사용한 조합 생성: 9명 중 7명을 골라야 한다는 것은, 9명 중 2명을 빼면 된다는 것과 같습니다. 이를 위해 누적합을 사용하여 두 명의 인덱스를 선택하는 방식을 사용했습니다.
  • next_permutation을 이용한 조합 생성: 이 라이브러리를 사용하여 순열을 만드는 방법을 적용했습니다. 9명 중 2명을 빼야 하는데, 0과 1로 이루어진 배열을 생성하여 0인 경우에 해당 인덱스를 선택하도록 했습니다.

 누적합이란

  • 누적합은 특정 부분의 합을 구할 때 자주 사용되는 방법입니다. 이 문제에서는 두 명을 뺐을 때의 전체 합을 바로 구할 수 있도록 하는 데 사용하였습니다.

 

3. 문제풀이 코드

코드 1: 재귀를 이용한 조합 생성

#include<bits/stdc++.h>
using namespace std;

// n이 9니까(10 이하니까) 무식한 방법으로 전체 경우의 수를 다 보며 조합 찾기 가능 
int tall[10];
int psum; // 전체 난쟁이 키의 합을 저장한다

void print(int minA, int minB){
    for(auto i: tall) if(i != minA && i != minB && i != 0) cout << i << '\n';
    exit(0);
}

void combi(int start, vector<int> &minArr){ // 시작할 인덱스, 제외할 인덱스 저장하는 배열
    if(minArr.size() == 2){
        int curPsum = psum;
        for (auto i : minArr) curPsum -= tall[i];

        if(curPsum == 100){
            int minA = tall[minArr[0]];
            int minB = tall[minArr[1]];   
            print(minA, minB);
        }
        return;
    }

    for(int i = start + 1; i < 9; i++){
        minArr.push_back(i);
        combi(i, minArr);
        minArr.pop_back();
    }

    return;
}   

int main(){
    cin.tie(0);
    ios::sync_with_stdio(0);
    
    vector<int> minArr;

    // 9명 난쟁이 키를 입력받는다 
    for(int i = 0; i < 9; i++){
        cin >> tall[i];
        psum += tall[i];
    }

    // 9명 난쟁이의 키를 오름차순으로 정렬한다
    sort(tall, tall + 9);

    // 7명 난쟁이의 합이 100이 되는 조합을 찾는다
    combi(-1, minArr);

    return 0;
}

 

코드 2: next_permutation을 사용한 조합 생성

 

#include<bits/stdc++.h>

using namespace std;

int tall[9];
int tallSum;

void makeCombi(int combiNum){
    vector<int> combi(9, 0);
    fill_n(combi.begin(), combiNum, 1);

    // `next_permutation`을 사용하기 전에 필수적인 오름차순 정렬 수행
    sort(tall, tall + 9);

    do{ 
        int t = tallSum;
        vector<int> d; 
        
        for(int i = 0; i < 9; i++){
            if(combi[i]) {
                t -= tall[i];
                d.push_back(tall[i]);
            }            
        }

        if(t == 100){  
            for(auto i: tall) if(find(d.begin(), d.end(), i) == d.end()) cout << i << '\n';
            exit(0);
        }

    } while(next_permutation(combi.begin(), combi.end()));
}

int main(){
    cin.tie(0);
    ios::sync_with_stdio(0);
    
    for(int i = 0; i < 9; i++){
        cin >> tall[i]; // 난쟁이 키 입력받기
        tallSum += tall[i];
    }

    makeCombi(2);
    
    return 0;
}

 

 

3-1. 코드 로직 설명

코드 1: 재귀를 사용한 조합 생성

  1. 재귀 함수 combi
    • minArr에 두 명의 인덱스를 저장하여 이 두 명을 제외하고 난쟁이들의 키를 합산합니다.
    • 이 합이 100이 되면, print 함수를 호출하여 제외된 두 명을 제외한 난쟁이들의 키를 출력합니다.
  2. 중복 호출 방지
    • 이미 답을 찾으면 exit(0)을 사용하여 프로그램을 종료함으로써 더 이상의 불필요한 연산을 방지합니다.
  3. 오름차순 출력
    • 출력 전 난쟁이들의 키를 오름차순으로 정렬하여 사전순으로 가장 먼저 오는 조합을 출력합니다.

 

코드 2: next_permutation을 사용한 조합 생성

  1. 0, 1 배열 생성
    • combi 배열을 생성하여 0과 1로 채웁니다. 1의 개수를 2로 설정하여 2명을 제외하는 조합을 생성합니다.
  2. next_permutation을 사용하여 조합 생성
    • 0과 1 배열을 next_permutation을 통해 가능한 조합을 모두 생성합니다.
    • 조합을 확인하여 합이 100이 되는 경우를 찾아 출력하고 프로그램을 종료합니다.

   +) 시간 복잡도

  • next_permutation은 모든 가능한 순열을 탐색하여, 이 코드에서 발생가능한 경우의 수는 9! 입니다.

 

4. 문제풀이에 사용된 함수

fill_n

 

배열을 특정 값으로 초기화하는 데 사용됩니다. 여기서는 combi 배열에 1을 채워 조합을 생성하는 데 사용했습니다.

fill_n(시작 위치, 초기화 시킬 요소 개수, 초기화 시킬 값)

 

exit(0)

 

프로그램을 즉시 종료하여 더 이상의 불필요한 연산을 방지합니다.


5. 요약 정리

 

  • 이 문제는 단순히 9명의 난쟁이 중에서 2명을 빼는 조합을 찾는 문제입니다.
  • 재귀 를 이용해 모든 조합을 탐색하거나, next_permutation 을 이용하여 조합을 생성하는 두 가지 방법을 활용할 수 있습니다.
  • 핵심은 9명 중 2명을 제외하여 나머지 7명의 키의 합이 100이 되는 경우를 찾는 것입니다.
  • 조합을 찾은 후에는 프로그램을 종료하여 불필요한 추가 연산을 방지해야 합니다.
  • next_permutation을 통해 순열을 활용하는 방법은 문제를 효율적으로 해결할 수 있는 좋은 방법입니다.
  • 9명의 난쟁이 수가 작기 때문에, 모든 조합을 직접 찾아도(=완전탐색 _ 브루트포스, 재귀) 시간 내에 충분히 해결할 수 있습니다. 일반적인 경우 1초에 처리할 수 있는 연산의 수는 약 10^8 이므로... 

 

+) n이 커지는 경우 | 부분집합의 합 문제에서 사용가능한 알고리즘

     = DP, 백트래킹, 그리디, 분할정복, 비트 마스킹

'백준 문제풀이' 카테고리의 다른 글

[백준] 2559번 : 수열 | C++  (0) 2024.09.17
[백준/boj] 9996번 : 한국이 그리울 땐 서버에 접속하지 | C++  (0) 2024.09.17
[백준/boj] 1991번: 트리 순회 | C++  (2) 2024.04.15
[백준/boj] 1043번: 거짓말 | C++  (0) 2024.02.26
[백준/boj] 11501번: 주식 | C++  (0) 2024.02.23
'백준 문제풀이' 카테고리의 다른 글
  • [백준] 2559번 : 수열 | C++
  • [백준/boj] 9996번 : 한국이 그리울 땐 서버에 접속하지 | C++
  • [백준/boj] 1991번: 트리 순회 | C++
  • [백준/boj] 1043번: 거짓말 | C++
c_jm
c_jm
  • c_jm
    c_jm
    c_jm

    🎋 어제보다 발전한 오늘

  • 전체
    오늘
    어제
    • 분류 전체보기 (36)
      • 프로젝트 (6)
      • 백준 문제풀이 (19)
      • 프로그래머스 문제풀이 (4)
      • 공부 (1)
      • 문제 해결 (2)
      • 기타 (4)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    docker
    nginx
    reverse proxy
    글자색
    백준 #1152 #c++
    도커
    도커컴포즈
    복붙
    코드블럭
    리버스 프록시 서버
    docker-compose
    jquery
    홈서버
    인라인 css
    docker-compose.yml
    다크모드
    리버스 프록시
    html
    티스토리 다크모드
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
c_jm
[백준/boj] 2309번: 일곱 난쟁이 | C++
상단으로

티스토리툴바