문제 설명
한 회사에 총 n명의 직원이 있다고 가정해 보겠습니다. 모든 직원은 실력에 따라 등급을 부여받으며, 등급은 1부터 k까지의 번호로 매겨집니다. 등급 i를 가진 직원의 수는 배열 skill에 저장되며, skill[i]는 등급 i를 가진 직원 수를 나타냅니다.
이제 회사에 새로운 지점이 개설되어, 실력이 서로 다른 직원들을 해당 지점으로 전출해야 하는 상황입니다. 새 지점에 보내야 할 직원 수는 m명입니다. 다양한 실력을 가진 m명의 직원을 새 지점에 배치하려면, 다음 공식을 최소화하는 지점의 직원 등급 배분표 branch를 구해야 합니다.
최소화할 공식: max(branch[i]/m − skill[i]/n)
단, branch[i] 값들의 합은 반드시 m이 되어야 하며, 우리가 구해야 할 것은 branch[i]의 각 원소입니다.
예를 들어 입력이 k = 5, n = 10, m = 25, skill = {5, 3, 2, 7, 4}라면, 출력은 12 7 5 17 10이 됩니다.
풀이 단계
이 문제는 비례 배분(largest remainder method) 기법으로 해결할 수 있습니다. 핵심 아이디어는 다음과 같습니다.
- 각 등급의 이상적인 배정 인원을 skill[i] × m / n으로 계산합니다.
- 계산값의 정수 부분을 우선 배정하고, 그 합을 sum에 누적합니다.
- m − sum만큼의 인원이 남으면, 소수 부분이 큰 순서대로 해당 등급에 1명씩 추가 배정합니다.
이를 의사 코드로 표현하면 다음과 같습니다.
sum := 0 for initialize i := 0, when i < k, update (increase i by 1), do: skill[i] := skill[i] * m / n 정수 쌍(pair)을 담는 배열 a 정의 for initialize i := 0, when i < k, update (increase i by 1), do: c := skill[i] sum := sum + c a[i]의 첫 번째 값 := skill[i] - c a[i]의 두 번째 값 := i 배열 a를 정렬 배열 a를 뒤집기(내림차순) for initialize i := 0, when i < m - sum, update (increase i by 1), do: skill[a[i]의 두 번째 값] := skill[a[i]의 두 번째 값] + 1 for initialize i := 0, when i < k, update (increase i by 1), do: skill[i] 출력
C++ 구현 예제
더 나은 이해를 돕기 위해 위 알고리즘을 C++로 구현한 코드를 살펴보겠습니다.
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
void solve(int k, int n, int m, vector<double> skill){
int sum = 0;
for (int i = 0; i < k; i++)
skill[i] = skill[i] * m / n;
vector<pair<double, int>> a(k);
for (int i = 0; i < k; i++) {
int c = skill[i];
sum += c;
a[i].first = skill[i] - c;
a[i].second = i;
}
sort(a.begin(), a.end());
reverse(a.begin(), a.end());
for (int i = 0; i < m - sum; i++) {
skill[a[i].second] += 1;
}
for (int i = 0; i < k; i++) {
if (i != k - 1)
cout << int(skill[i]) << " ";
else
cout << int(skill[i]) << endl;
}
}
int main() {
int k = 5, n = 10, m = 25;
vector<double> skill = {5, 3, 2, 7, 4};
solve(k, n, m, skill);
return 0;
}
입력
5, 10, 25, {5, 3, 2, 7, 4}
출력
12 7 5 17 10
코드 동작 원리
위 코드의 동작 과정을 단계별로 살펴보면 다음과 같습니다.
- 비례 계산: skill[i] × m / n을 통해 각 등급이 이론적으로 담당해야 할 인원을 구합니다. 예제에서는 배율이 25/10 = 2.5배이므로 {12.5, 7.5, 5.0, 17.5, 10.0}이 계산됩니다.
- 정수 부분 배정: 각 값의 정수 부분인 {12, 7, 5, 17, 10}을 우선 배정하고, 그 합을 sum에 저장합니다.
- 잔여 인원 처리: m에서 sum을 뺀 만큼의 인원이 남으면, pair 배열에 저장해 둔 소수 부분(first)이 큰 순서대로 해당 등급(second, 원래 인덱스)에 1명씩 추가 배정합니다. 이렇게 하면 전체 배분이 목표 인원 m과 정확히 일치하게 됩니다.
이 방식은 각 등급 간 비율의 균형을 유지하면서도 전체 오차를 최소화할 수 있어, 제한된 인원을 여러 그룹에 공평하게 나누는 다양한 상황에 응용할 수 있습니다.