개발
[백준] 30802번 - 웰컴키트

문제 탐색하기
1) 문제 분석
모든 참가자들에게 티셔츠 한 장, 펜 한 자루를 배부.
티셔츠 종류 : 6가지 (S, M, L, XL, XXL, XXXL)
펜 종류 : 1가지 (P자루씩 묶음 or 한 자루)
티셔츠는 남아도 되지만, 부족해서는 안 되고, 신청한 사이즈대로 배부
펜은 남거나 부족해서는 안 되고, 정확히 참가자 수만큼 준비
목표 : 티셔츠를 T장씩 최소 몇 묶음 주문해야 하는지, 펜을 P자루씩 최대 몇 묶음 주무할 수 있고, 펜을 한 자루씩 몇 개 주문해야 하는지 구하기
2) 가능한 시간복잡도
(1) 입력 처리
전체 인원 N, 6개의 사이즈 별 인원 수, 티와 펜의 묶음 수 T, P -> O(1)
입력한 수를 정수 처리 -> O(1)
(2) 사이즈 별로 필요한 티셔츠 묶음 수 계산 -> O(6) -> O(1)
(3) 펜 묶음 최대 개수, 낱개 펜 개수 계산 -> O(1)
(4) 결과 출력 -> O(1)
(5) 전체 시간복잡도
입력 처리 : O(1)
사이즈 별 계산 : O(1)
펜 계산 : O(1)
결과 출력 : O(1)
전체 시간복잡도 : O(1)
3) 알고리즘 선택 이유
이 문제는 각 티셔츠 사이즈별로 필요한 묶음 수와 참가자 수에 따른 펜 묶음 계산을 단순하게 처리하는 문제로, 복잡한 탐색이나 자료구조가 필요하지 않은 단순 수학적 계산 기반 문제이다.
각 사이즈별 티셔츠 인원 수는 고정된 6개이므로, 이를 순차적으로 계산하는 방식이 가장 직관적이고 효율적이다. 또한 참가자 수 N과 펜 묶음 수 P를 활용한 몫과 나머지 계산만으로 답을 구할 수 있기 때문에, 조건 분기 없이 수학적 연산만으로 문제를 해결하는 O(1) 완전탐색 방식이 최적이다.
따라서, 입력 크기가 작고 계산이 단순하기 때문에 완전탐색(브루트포스) 기반의 직관적 접근이 가장 자연스럽고 최적이다.
잠깐! 브루트포스에 대한 간단한 설명, 그리고 관련 예시 문제는 여기를 참고하자.
코드 설계하기
1. 실행 구조
1) 사용자의 빙고판과 사회자가 부르는 숫자 입력
첫번째 줄 : 정수형으로 참가자 수 N에 저장한다.
두번째 줄 : 공백을 기준으로 분리하여, 정수형으로 티셔츠 사이즈 별 신청자 수 배열 Size에 저장한다.
세 번째 줄 : 공백을 기준으로 분리하여, 정수형으로 티셔츠 묶음 수 T, 펜 묶음 수 P에 저장한다.
2) 각 사이즈별로 필요한 티셔츠 묶음 수 계산
Sizes의 길이만큼 반복한다.
만약 Sizes를 티셔츠 묶음 수인 T로 나누었을 때, 나머지가
0이라면 몪만큼 더하기
0이 아니라면, 몫에 1을 더하기
3) 펜 묶음 최대 개수와 낱개 펜 개수 계산
전체 인원 수 N을 펜 묶음 수 P로 나누어 펜 묶음 최대 개수를 구한다.
전체 인원수 N에서 펜 묶음 수 P와 펜 묶음 최대 개수를 곱한 값을 빼 남은 펜 낱개 개수를 구한다.
4) 결과를 출력한다.
2. 고민이 되었던 부분
: Number 타입으로 변환된 값들을 가지고 개수를 구할 때 사용해보니, 나눗셈(/)을 하였을 때 몫만 구하고 싶었지만, 소숫점까지 구해지는 문제가 있었다. 소숫점을 제외한 몫만 구하는 방법에 대해서 고민이 됐었다. 그러다 Math 함수의 floor(), ceil() 메소드를 알게 되었고, 이를 가지고 결과를 쉽게 구할 수 있었다.
더 나아가 floor(), ceil(), round() 메소드에 대한 차이가 궁금해졌고, 이에 대해서 짧게라도 공부할 수 있었다.
1) 비교 표
입력값 | Math.floor() | Math.ceil() | Math.round() |
2.1 | 2 | 3 | 2 |
2.5 | 2 | 3 | 3 |
2.9 | 2 | 3 | 3 |
3.0 | 3 | 3 | 3 |
2) 언제 어떤 걸 써야 하나요?
목적 | 함수 |
남는 게 있어도 올려서 처리해야 할 때 (예: 티셔츠 묶음 계산) | Math.ceil() |
가장 가까운 정수로 반올림 (정확한 수치 근사화) | Math.round() |
내림하여 정수 몫 계산 (남는 거 무시) | Math.floor() |
정답 코드
// 사용 언어 : Javascript
// 입력 데이터 읽기 및 전처리
const fs = require('fs');
const input = fs.readFileSync('/dev/stdin').toString().trim().split("\n");
const N = Number(input[0]); // 참가자 수
const Sizes = input[1].split(' ').map(Number); // 티셔츠 사이즈별 신청자 수
const [T, P] = input[2].split(' ').map(Number); // 티셔츠 묶음 수, 펜 묶음 수
let total_T = 0; // 티셔츠 묶음 총 개수
// 각 사이즈별로 필요한 티셔츠 묶음 수 계산
for (let i = 0; i < Sizes.length; i++){
if (Sizes[i] % T === 0)
total_T += Sizes[i] / T; // 딱 맞게 떨어지면 몫만큼 더함
else {
let count = Math.ceil((Sizes[i] / T)); // 남으면 한 묶음 더 필요
total_T += count;
}
}
// 펜 묶음 최대 개수와 낱개 펜 개수 계산
let total_P = Math.floor(N / P); // 펜 묶음 최대 개수
let total_P_alone = N - (P * total_P); // 남은 펜 낱개 개수
console.log(total_T); // 티셔츠 묶음 수 출력
console.log(total_P + " " + total_P_alone); // 펜 묶음 수와 낱개 수 출력마무리하며
처음 이 문제를 접했을 때는 단순한 나눗셈 문제라고 생각했다. 하지만 문제를 정확히 읽고 분석해보니, 티셔츠와 펜이라는 두 가지 항목 모두 남거나 부족해서는 안 되는 조건이 있었고, 이에 따라 어떻게 계산하느냐에 따라 전혀 다른 결과가 나올 수 있다는 점이 핵심이었다.
특히 티셔츠 묶음을 계산할 때는 남는 인원이 있어도 한 묶음을 추가해야 하는 상황이기 때문에, Math.floor()가 아닌 Math.ceil()을 사용해야 한다는 사실이 중요했다. 처음에는 단순히 나눗셈을 하다 보니 실수값이 나왔고, Math.floor()와 Math.ceil()의 차이에 대해 고민하며 예제를 비교해보는 과정에서 단순한 연산 하나에도 분명한 목적과 기준이 필요하다는 것을 배울 수 있었다.
이번 문제를 풀면서 새삼 느낀 것은, 문제를 정확히 이해하는 힘과 구현의 정확성이 결국 실력을 좌우한다는 점이었다.
단순히 답을 맞히는 것을 넘어, 어떤 연산이 왜 필요한지, 어떤 방식이 더 직관적인지를 끊임없이 고민하는 습관이 앞으로의 개발자로서의 역량을 키우는 데 중요한 자산이 될 것이다.
앞으로도 빠르게 푸는 것에만 집중하지 않고, 명확하고 좋은 코드를 작성하기 위해 더 깊이 있게 문제를 바라보는 태도를 유지하고 싶다.