Programming/백준

[골드 2] 백준 2878 - 캔디캔디 (파이썬)

pental 2025. 6. 25. 20:40

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

풀이

총 M개의 캔디를 N명의 학생에게 나눠줘야 한다.

각 학생은 자신이 원하는 캔디 수가 있고, 그보다 적게 받으면 “화남 지수”가 받은 개수^2로 증가한다.

M개를 어떻게 배분해야 전체 화남 지수의 합이 최소가 되도록 하는지가 문제이다.

사용한 풀이 전략은 다음과 같다.

1. 이분 탐색을 이용해 평균 컷 구하기

  • 학생마다 min(A[i], mid) 만큼 주는 방식을 생각한다.
  • 이 때 mid를 조절하며, 총 줄 수 있는 캔디가 M 이상이 되는 최소 mid를 찾는다.
  • mid 이상을 주면 안 되고, 모든 학생에게 최대한 공평하게 적게 주자는 전략.

2. 이분 탐색 후 배분

  • 최적의 컷 수 p를 찾았으면,
  • 모든 학생에게 min(p - 1, A[i])만큼 먼저 배분한다.
  • 남은 캔디가 있다면, A[i] > 0인 학생에게 하나씩 더 나눠준다.

3. 화남 지수 계산

  • 학생별로 받은 캔디 수의 제곱을 합해 최종 정답을 구한다.
M, N = map(int, input().split())
A = [int(input()) for _ in range(N)]

M은 전체 캔디 수, N은 학생 수, A는 각 학생이 원하는 캔디 수이다.

debt = max(0, sum(A) - M)
low, high, p = 0, max(A), -1

while low <= high:
    mid = (low + high) // 2
    pay_back = sum(min(mid, x) for x in A)

    if debt <= pay_back:
        p = mid
        high = mid - 1
    else:
        low = mid + 1

pay_back은 각 학생이 mid 이상 못 받게 했을 때 줄 수 있는 총량이다.

debt는 총 원하는 양 - M, 즉 못 주는 양이다.

p를 찾는 이유는 학생들이 p이상 받으면 안되게끔 제한하기 위해서 사용된다.

캔디를 배분한다.

angry = [0] * N
for i in range(N):
    if A[i] >= p - 1:
        debt -= p - 1
        angry[i] += p - 1
        A[i] -= p - 1
    else:
        debt -= A[i]
        angry[i] += A[i]
        A[i] = 0

각 학생에게 먼저 p - 1 또는 가능한 만큼 배분한다.

if debt:
    for i in range(N):
        if debt > 0 and A[i] > 0:
            debt -= 1
            angry[i] += 1
            A[i] -= 1

그리고 남은 캔디가 있으면 하나씩 더 준다.

그리고 화남 지수를 계산해준다.

answer = 0
for i in range(N):
    answer += (angry[i] ** 2) % mod

print(answer)

각 학생의 화남 지수는 (받은 캔디 수) ** 2이고, 이를 모두 더해서 출력한다.

결론적으로, 이 문제는 단순히 그리디하게 큰 학생부터 나눠주는 것이 아닌, 캔디 개수의 상한선을 이분 탐색으로 찾고, 그 기준으로 나눠주는 공평한 분배 전략이 필요하다.

코드

# 백준 2878 - 캔디캔디
# 분류 : 이분 탐색

M, N = map(int, input().split())
A = [int(input()) for _ in range(N)]

mod = 2 ** 64

debt = max(0, sum(A) - M)

low, high, p = 0, max(A), -1

while low <= high :
    mid = (low + high) // 2

    pay_back = 0
    for i in range(N) :
        if A[i] >= mid :
            pay_back += mid
        else :
            pay_back += A[i]

    if debt <= pay_back :
        p = mid
        high = mid - 1
    else :
        low = mid + 1

# p, p - 1, A[i]

angry = [0] * N
for i in range(N) :
    if A[i] >= p - 1 :
        debt -= p - 1
        angry[i] += p - 1
        A[i] -= p - 1
    else :
        debt -= A[i]
        angry[i] += A[i]
        A[i] = 0

if debt :
    for i in range(N) :
        if debt > 0 and A[i] > 0 :
            debt -= 1
            angry[i] += 1
            A[i] -= 1

answer = 0
for i in range(N) :
    answer += (angry[i] ** 2) % mod

print(answer)