# 10-1. 조합 알아보기
조합(Combination)이란?
- nCr로 표현하며, n개의 숫자 중에서 r개를 뽑는 경우의 수
- 순열은 nPr로 표현되며, n개의 숫자 중 r개를 뽑아 순서를 고려해 나열할 경우의 수를 이야기 한다.
조합의 핵심 이론
- 순열 공식 nPr = n!/(n-r)!
- 조합 공식 nCr = n!/(n-r)!r!
- 알고리즘에서 조합을 구현할 때는 위 공식을 코드화하지 않고 점화식을 사용해 표현한다.
- 예를 들어, 5개의 데이터 중 3개를 선택하는 경우는 5개의 데이터 중 4개의 데이터의 선택 여부가 결정된 상태로 가정한다.
- 따라서 5번째 데이터의 선택 여부에 따라 이전 4개의 데이터 중 2개 혹은 3개를 선택하는 경우의 수로 나뉜다.
- 위를 점화식으로 표현하면 D[5][3] = D[4][2]+D[4][3]이 된다.
- 위 과정을 바탕으로 일반 점화식을 도출할 수 있다. D[i][j] = D[i-1][j] + D[i-1][j-1]
https://www.acmicpc.net/problem/11050
import sys
input = sys.stdin.readline
N, K = map(int, input().split())
D = [[0 for j in range(N+1)] for i in range(N+1)]
for i in range(0, N+1): # DP 테이블 초기화
D[i][1] = i # i개에서 1개 선택하는 경우의 수는 i개
D[i][0] = 1 # i개에서 1개도 선택하지 않는 경우의 수는 1개
D[i][i] = 1 # i개에서 모두 선택하는 경우의 수는 1개
for i in range(2, N+1):
for j in range(1, i):
D[i][j] = D[i-1][j]+D[i-1][j-1] # 조합 점화식
print(D[N][K])
- N,K의 값을 입력받고 DP 테이블을 초기화 한다.
- 점화식으로 DP 테이블의 값을 채운 후 D[N][K]의 값을 출력한다.
- 이때 j는 i값(선택 수, 총 개수)을 넘지 않는다. 즉, 대각선 윗부분은 채워지지 않음.
https://www.acmicpc.net/problem/11051
- N의 범위가 커지고 결괏값을 10,007로 나눈 나머지를 출력한다.
import sys
input = sys.stdin.readline
N, K = map(int, input().split())
D = [[0 for j in range(N+1)] for i in range(N+1)]
for i in range(0, N+1):
D[i][1] = i # i개에서 1개 선택하는 경우의 수는 i개
D[i][0] = 1 # i개에서 1개도 선택하지 않는 경우의 수는 1개
D[i][i] = 1 # i개에서 모두 선택하는 경우의 수는 1개
for i in range(2, N+1):
for j in range(1, i):
D[i][j] = D[i-1][j]+D[i-1][j-1] # 조합 점화식
D[i][j] = D[i][j]%10007 # mod 연산
print(D[N][K])
- 모듈러 연산의 특성 : (A mod N + B mod N) mod N = (A+B) mod N
- DP 테이블에 결괏값이 나올 때마다 모듈러 연산을 수행한다.
https://www.acmicpc.net/problem/2775
import sys
input = sys.stdin.readline
D = [[0 for j in range(15)] for i in range(15)]
for i in range(1, 15):
D[i][1] = 1 # i층의 1호는 항상 1의 값을 지니므로 초기화 가능
D[0][i] = i # 0층의 i호는 i의 값을 지니도록 주어짐
for i in range(1, 15):
for j in range(2, 15):
D[i][j] = D[i][j-1]+D[i-1][j] # 점화식
T = int(input())
for i in range(0, T): # D 테이블을 모두 구해 놓은 후 질의에 대한 답 출력
K = int(input())
N = int(input())
print(D[K][N])
- a층의 b호에 살려면 자신의 아래층 (a-1)층의 1호부터 b호까지 사람들의 수의 합만큼 사람들을 데려와 살아야 한다.
- 즉, D[a][b] = D[a-1][0]+...+D[a-1][b-1]+D[a-1][b]로 나타낼 수 있다. 이 내용을 응용하면 a층 b호는 a층 b-1호의 값에서 자기 아래층(a-1층 b호)의 사람 수만 더하면 된다.
- 이를 일반화된 점화식으로 나타내면 D[a][b] = D[a][b-1] +D[a-1][b]가 된다.
https://www.acmicpc.net/problem/1010
import sys
input = sys.stdin.readline
D = [[0 for j in range(31)] for i in range(31)]
for i in range(1, 31):
D[i][1] = i # i개에서 1개 선택 경우의 수는 i개
D[i][0] = 1 # i개에서 1개도 선택하지 않는 경우의 수는 1개
D[i][i] = 1 # i개에서 모두 선택하는 경우의 수는 1개
for i in range(2, 31):
for j in range(1, i):
D[i][j] = D[i-1][j]+D[i-1][j-1] # 점화식
T = int(input())
for i in range(0, T): # D 테이블을 모두 구해 놓은 후 질의에 대한 답 출력
N, M = map(int, input().split())
print(D[M][N])
- M개의 사이트에서 N개를 선택하는 조합 문제로 변경한다.
- D[31][31]로 DP 테이블을 선언하고 초기화 한다.
- 점화식을 이용해 DP 테이블을 채운다. M(행)개 중 N(열)개를 뽑는 것임.
https://www.acmicpc.net/problem/13251
- 단순 확률식으로 풀 수 있지만 테이블을 이용해서 풀어보자.
D = [0]*51
prob = [0]*51
M = int(input())
D = list(map(int, input().split()))
T = 0
for i in range(0, M):
T += D[i] # 조약돌 개수 더하기
K = int(input())
ans = 0
for i in range(0, M):
if D[i]>=K:
prob[i]=1
for k in range(0, K):
prob[i] = prob[i]*(D[i]-k)/(T-k) # 확률식
ans += prob[i]
print(ans)
- 색깔별 조약돌의 개수에서 K를 뽑을 수 있는 경우의 수를 구한 후, 전체 돌에 관해 n개를 뽑는 경우의 수를 나눈다.
- 색깔별 조약돌의 개수를 D테이블에 저장하고, 전체 조약돌의 개수 T를 구한다.
- 한 색깔의 조약돌만 뽑을 확률을 색깔별로 구한다. 입력 K에 따라 구하기.
- 각 확률을 더해 출력한다.
https://www.acmicpc.net/problem/1722
- 첫줄에 입력받은 N자리로 만들 수 있는 순열의 경우의 수를 구해야 한다.
import sys
input = sys.stdin.readline
F = [0]*21
S = [0]*21
visited = [False]*21
N=int(input())
F[0] = 1
for i in range(1, N+1): # 팩토리얼 초기화 -> 각 자릿수에서 만들 수 있는 경우의 수
F[i] = F[i-1]*i
inputList = list(map(int, input().split()))
if inputList[0]==1:
K = inputList[1]
for i in range(1, N+1):
cnt = 1
for j in range(1, N+1):
if visited[j]: # 이미 사용한 숫자는 사용 X
continue
if K<=cnt*F[N-i]: # 주어진 K에 따라 각 자리에 들어갈 수 있는 수 찾기
K-=(cnt-1)*F[N-i]
S[i]=j
visited[j]=True
break
cnt+=1
for i in range(1, N+1):
print(S[i], end=' ')
else:
K = 1
for i in range(1, N+1):
cnt = 0
for j in range(1, inputList[i]):
if not visited[j]:
cnt += 1 # 미사용 숫자 개수만큼 카운트
K += cnt*F[N-i] # 자릿수에 따라 순서 더하기
visited[inputList[i]] = True
print(K)
- 먼저 자릿수에 따른 순열의 경우의 수를 1부터 N자리까지 미리 계산한다. 4자리의 경우, list = [4!, 3!, 2!, 1!]로 저장이 된다. 이후 문제에 따라 2가지 경우로 나뉜다.
- K번째 순열 출력하기
- 주어진 값 K와 현재 자리(N)-1에서 만들 수 있는 경우의 수를 비교한다.
- if K<=cnt*F[N-i]에 해당하지 않는다면 cnt +1(아래)
- K가 더 작아질 때까지 경우의 수를 배수(cnt)로 증가한다.
- K가 더 작아지면 순열에 값을 저장하고, K를 K-(경우의 수 * (cnt-1))로 업데이트 한다.
- 순열이 완성될 때까지 위 과정을 반복한다.
- 주어진 값 K와 현재 자리(N)-1에서 만들 수 있는 경우의 수를 비교한다.
- 입력된 순열의 순서 구하기
- 현재 자릿수의 숫자를 확인하고 해당 숫자보다 앞 숫자 중 미사용 숫자를 카운트 한다.
- 미사용 숫자 개수*(현재 자리 -1에서 만들 수 있는 순열의 개수)를 K에 더한다.
- 모든 자릿수에 관해 위 과정을 반복한 후 K값을 출력한다.
- 개인적으로 너무 어려웠다....플레보다....아래 참고
https://karla.tistory.com/43#--%--K%EB%B-%--%EC%A-%B-%--%EC%--%-C%EC%--%B-%--%EC%B-%-C%EB%A-%A-
https://www.acmicpc.net/problem/1256
- a와 z의 개수가 각각 N,M개일 때 이 문자들로 만들 수 있는 모든 경우의 수는 N+M개에서 N개 혹은 M개를 뽑는 경우의 수와 동일하다.
import sys
input = sys.stdin.readline
N,M,K = map(int, input().split())
D = [[0 for j in range(202)] for i in range(202)]
for i in range(0, 201): # 조합 테이블 만들기
for j in range(0, i+1):
if j==0 or j==i:
D[i][j]=1
else:
D[i][j] = D[i-1][j-1] + D[i-1][j]
if D[i][j] > 1000000000:
D[i][j] = 1000000001 # K값의 범위가 넘어가면 최댓값 저장
if D[N+M][M]<K: # 주어진 자릿수로 만들 수 없는 K번째 숫자의 경우
print(-1)
else:
while not(N==0 and M==0):
if D[N-1+M][M]>=K: # a를 선택해도 남은 경우의 수가 K보다 큰 경우
print("a", end='')
N-=1
else:
print("z", end='')
K-=D[N-1+M][M]
M-=1
- 조합의 경우의 수를 나타내는 DP테이블을 초기화하고 점화식으로 저장한다.
- D[i][j] = D[i-1][j-1] + D[i-1][j]
- 몇 번째 문자열을 표현해야 하는지 나타내는 변수를 K라 한다. 현재 자릿수에서 a를 선택했을 때 남아있는 문자들로 만들 수 있는 모든 경우의 수를 T라고 지정한다. T와 K를 비교해 문자를 선택한다.
- T >= K : 현재 자리 문자를 a로 선택
- T < K : 현재 자리 문자를 z로 선택하고, K=K-T로 업데이트
- 위 과정으로 a와 z의 문자들의 수를 합틴만큼 반복해 정답 문자열을 출력한다.
https://www.acmicpc.net/problem/1947
- 완전 순열 : n개의 원소의 집합에서 원소들을 재배열할 때 이전과 같은 위치에 배치되는 원소가 1개도 없을 때를 말한다.
N = int(input())
mod = 1000000000
D = [0]*1000001
D[1] = 0
D[2] = 1
for i in range(3, N+1):
D[i] = (i-1)*(D[i-1]+D[i-2])% mod # 완전 순열 점화식
print(D[N])
- D[N] 테이블을 N명일 때 선물을 교환할 수 있는 모든 경우의 수로 정한다. N명이 존재하며, A가 B에게 선물을 주었을 때 교환 방식은 2가지가 존재한다.
- B도 A에게 선물을 줌(양방향) : 남은 경우의 수는 D[N-2]
- B는 A에게 주지 않음(단방향) : 남은 경우의 수는 D[N-1], B만 받은 선물이 전해진 것!
- A는 (N-1)명의 학생에게 선물을 전달할 수 있으므로, 점화식을 도출하면 D[i] = (i-1)*(D[i-1]+D[i-2])가 된다.
- DP테이블을 초기화 한 후 완전 순열 점화식으로 정답을 구한다.
# 회고
조합 너무어렵다ㅜㅜ
조합은 진짜 브론즈문제부터 차근차근...해야할 듯. 점화식을 도출해내는 머리가 부족한 듯 하다. 🥲🥲🥲🥲🥲
'코딩테스트' 카테고리의 다른 글
[Python] 조합 문제 풀이 정리(2) (0) | 2024.05.30 |
---|---|
[Python] 조합 문제 풀이 정리(1) (0) | 2024.05.29 |
[Python] 트리 문제 풀이 정리 (0) | 2024.05.21 |
[Python] 그래프 문제 풀이 정리(1) (0) | 2024.05.19 |
[Python] 트리 (0) | 2024.05.17 |