비둘기집 원리 (Pigeonhole Principle) 는 “N+1 개의 객체를 N 개의 상자에 넣으면, 적어도 하나의 상자는 두 개 이상의 객체를 포함한다” 는 자명한 조합론 원리. Dirichlet 의 이름을 따 Dirichlet Principle 이라고도 함.
알고리즘 문제에서 “존재성 증명” 과 “최소한 하나는 ~ 하다” 를 주장할 때 핵심 도구.
문제 상황과 동기
단순해 보이지만 비둘기집 원리는 의외로 강력한 문제 해결 도구다.
직관적 사용: “367 명이 있으면 적어도 두 명은 생일이 같다” (366 일)
PS 응용: 부분집합 합 문제 (N 개 수에서 어떤 부분집합의 합이 M 의 배수)
극단 원리와 결합: 가장 큰 / 작은 것의 성질 + 비둘기집
핵심 통찰: 무언가가 많으면 반드시 겹치는 구석이 생긴다.
시각화
핵심 아이디어
기본 형태
If n items are placed into m boxes, and n > m,then at least one box contains at least ceil(n/m) items.
일반화 (강한 형태)
If n items are placed into m boxes, thenthere exists a box with at least ceil(n/m) items,and a box with at most floor(n/m) items.
대우 (contrapositive)
"각 상자에 최대 1 개" = 총 객체 수 ≤ 상자 수
존재 증명: 직접 찾지 않고도 반드시 존재한다고 주장 가능.
알고리즘
비둘기집 원리 적용 패턴:1. N 개의 객체를 M 개의 "종류" 로 분류 (M < N)2. 한 종류에 최소 2 개 이상이 존재함을 주장3. 그 종류가 원하는 성질을 가짐을 보임대표 예시: "수열의 부분합 중 M 의 배수" - 누적 합 S[0..N] (N+1 개) - 각 S[i] mod M → M 가지 나머지 - N+1 > M → 같은 나머지 S[a] ≡ S[b] (mod M) - 구간 (a, b] 의 합 = S[b] - S[a] ≡ 0 (mod M)
구현
// 비둘기집 원리: 부분집합 합이 M 의 배수인 경우 찾기// 누적 합이 같은 나머지 -> 구간 합이 0 (mod M)#include <bits/stdc++.h>using namespace std;int main() { int n, m; cin >> n >> m; vector<int> a(n); for (auto& v : a) cin >> v; // prefix sum mod m vector<int> first(m, -1); // 각 나머지의 첫 등장 위치 first[0] = 0; // 빈 구간 int sum = 0; for (int i = 0; i < n; i++) { sum = (sum + a[i]) % m; if (first[sum] != -1) { int l = first[sum] + 1, r = i + 1; cout << "Found: [" << l << ", " << r << "]\n"; // 부분 수열 a[l-1..r-1] 의 합이 M 의 배수 return 0; } first[sum] = i + 1; } // 비둘기집에 의해 여기 도달 불가 (N+1 > M 이면) cout << "No subset (requires N >= M)" << "\n";}
# 비둘기집 원리: 구간 합이 M 의 배수def find_mod_zero_subarray(arr, m): first = {0: -1} # prefix sum % m -> index pref = 0 for i, x in enumerate(arr): pref = (pref + x) % m if pref in first: l = first[pref] + 1 r = i return (l, r) # arr[l..r] 합이 m 의 배수 first[pref] = i return None# 비둘기집 응용: 수열에서 같은 값의 두 원소def has_duplicate(arr): return len(arr) != len(set(arr))n, m = map(int, input().split())a = list(map(int, input().split()))res = find_mod_zero_subarray(a, m)if res: l, r = res print(f"Found: [{l+1}, {r+1}]")else: print("Not found (n < m)")
// 비둘기집 원리: 나머지 배열로 부분배열 합 M 의 배수 찾기import java.util.*;import java.io.*;public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); int n = Integer.parseInt(st.nextToken()); int m = Integer.parseInt(st.nextToken()); int[] a = new int[n]; st = new StringTokenizer(br.readLine()); for (int i = 0; i < n; i++) a[i] = Integer.parseInt(st.nextToken()); int[] first = new int[m]; Arrays.fill(first, -1); first[0] = 0; int sum = 0; for (int i = 0; i < n; i++) { sum = (sum + a[i]) % m; if (first[sum] != -1) { int l = first[sum] + 1, r = i + 1; System.out.println("Found: [" + l + ", " + r + "]"); return; } first[sum] = i + 1; } System.out.println("Not found"); }}
stdin
5 32 4 1 7 5
결과
Found: [1, 3]
stdin
4 41 3 2 5
결과
Found: [1, 2]
복잡도
항목
값
비둘기집 사상 (mapping)
O(N)
존재 판정
O(N)
공간
O(M)
일반 확장 (Erdos–Ginzburg–Ziv)
O(N)
대표 응용
1. 생일 문제 (Birthday Problem)
23 명: 두 명 생일 같을 확률 > 50%366 명: 반드시 같은 생일 (비둘기집)
2. Erdős–Ginzburg–Ziv 정리
2n-1 개의 정수에서 적어도 n 개의 합이 n 의 배수.
3. 수열의 부분합
길이 N 수열, N 개 이상의 나머지 M 에 대해비둘기집으로 같은 나머지 구간 발견.
4. Ramsey 수의 하계
비둘기집 원리는 Ramsey 이론의 기본 빌딩 블록.R(3, 3) = 6 증명에 사용.
5. 중국인의 나머지 정리와 결합
서로소 M1, M2 ... 의 나머지 배열로 수를 복원.비둘기집은 주기의 겹침 증명.
함정
1. N > M 조건 착각
비둘기집이 적용되려면 객체 수가 상자 수보다 초과해야 함. 같으면 반드시 겹친다고 할 수 없음.
💬 댓글