트럼프 카드로 새로운 게임을 만들기로 한 정연이의 이야기로 시작해보자. 이 게임은 딜러와 플레이어가 1:1로 플레이하며, 플레이어는 52장의 트럼프 카드 중 N장을 뽑는다. 만약 뽑은 카드들로 “포카드(four of a kind)” 족보를 만들 수 있다면 플레이어의 승리, 그렇지 않다면 딜러의 승리로 게임이 끝난다. 여기서 포카드는 같은 숫자의 카드 4장을 의미한다.
정연이는 공정한 게임을 위해 N의 값을 결정하고자 한다. 이를 위해 N장의 카드를 뽑았을 때 플레이어가 이길 수 있는 경우의 수를 구하려고 한다. 우리의 목표는 N이 주어졌을 때, 플레이어가 이기는 경우의 수를 10,007로 나눈 나머지를 구하는 것이다.
#include<iostream>usingnamespacestd;constintMOD=10007;constintMAXN=52;intfactorial[MAXN+1];intinv_factorial[MAXN+1];// 모듈러 거듭제곱 함수: x^y mod MOD 계산
intmod_pow(intx,inty){intresult=1;x%=MOD;while(y>0){if(y%2==1)result=result*x%MOD;x=x*x%MOD;y/=2;}returnresult;}// 모듈러 역원 계산: n^(-1) mod MOD
intmod_inv(intn){returnmod_pow(n,MOD-2);}// 이항 계수 계산: nCr mod MOD
intnCr(intn,intr){if(r<0||r>n)return0;returnfactorial[n]*inv_factorial[r]%MOD*inv_factorial[n-r]%MOD;}intmain(){intN;cin>>N;// 팩토리얼과 역팩토리얼 미리 계산
factorial[0]=1;for(inti=1;i<=MAXN;++i)factorial[i]=factorial[i-1]*i%MOD;inv_factorial[MAXN]=mod_inv(factorial[MAXN]);for(inti=MAXN-1;i>=0;--i)inv_factorial[i]=inv_factorial[i+1]*(i+1)%MOD;intresult=0;intmaxK=N/4;if(maxK>13)maxK=13;// 최대 13개의 숫자만 존재
for(intk=1;k<=maxK;++k){intsign=(k%2==1)?1:-1;// 포함 배제의 부호 결정
intcomb1=nCr(13,k);// 포카드로 사용할 숫자 k개 선택
intremaining_cards=52-4*k;// 남은 카드 수
intremaining_draws=N-4*k;// 남은 뽑아야 할 카드 수
if(remaining_draws<0||remaining_draws>remaining_cards)continue;intcomb2=nCr(remaining_cards,remaining_draws);// 남은 카드에서 선택
intterm=sign*comb1%MOD*comb2%MOD;if(term<0)term+=MOD;// 음수 처리
result=(result+term)%MOD;}cout<<result<<endl;return0;}
코드 설명
팩토리얼 및 역팩토리얼 계산: 이항 계수를 빠르게 계산하기 위해 미리 팩토리얼과 모듈러 역원을 계산한다.
mod_pow 함수: 거듭제곱을 빠르게 계산하기 위한 함수로, 모듈러 역원을 구할 때 사용된다.
포함 배제 계산: k개의 포카드를 선택하는 경우에 대해 포함 배제를 적용하여 총 경우의 수를 계산한다.
#include<stdio.h>#define MOD 10007
#define MAXN 52
intfactorial[MAXN+1];intinv_factorial[MAXN+1];// 모듈러 거듭제곱 함수: x^y mod MOD 계산
intmod_pow(intx,inty){intresult=1;x%=MOD;while(y>0){if(y%2==1)result=result*x%MOD;x=x*x%MOD;y/=2;}returnresult;}// 모듈러 역원 계산: n^(-1) mod MOD
intmod_inv(intn){returnmod_pow(n,MOD-2);}// 이항 계수 계산: nCr mod MOD
intnCr(intn,intr){if(r<0||r>n)return0;returnfactorial[n]*inv_factorial[r]%MOD*inv_factorial[n-r]%MOD;}intmain(){intN;scanf("%d",&N);// 팩토리얼과 역팩토리얼 미리 계산
factorial[0]=1;for(inti=1;i<=MAXN;++i)factorial[i]=factorial[i-1]*i%MOD;inv_factorial[MAXN]=mod_inv(factorial[MAXN]);for(inti=MAXN-1;i>=0;--i)inv_factorial[i]=inv_factorial[i+1]*(i+1)%MOD;intresult=0;intmaxK=N/4;if(maxK>13)maxK=13;// 최대 13개의 숫자만 존재
for(intk=1;k<=maxK;++k){intsign=(k%2==1)?1:-1;// 포함 배제의 부호 결정
intcomb1=nCr(13,k);// 포카드로 사용할 숫자 k개 선택
intremaining_cards=52-4*k;// 남은 카드 수
intremaining_draws=N-4*k;// 남은 뽑아야 할 카드 수
if(remaining_draws<0||remaining_draws>remaining_cards)continue;intcomb2=nCr(remaining_cards,remaining_draws);// 남은 카드에서 선택
intterm=((sign*comb1)%MOD*comb2)%MOD;if(term<0)term+=MOD;// 음수 처리
result=(result+term)%MOD;}printf("%d\n",result);return0;}
코드 설명
stdio.h 사용: 표준 입출력을 위해 stdio.h만 사용하였다.
나머지 부분은 이전 코드와 동일: C++의 기능을 최소화하고 C 스타일로 작성하였다.
모든 변수와 함수는 C 스타일로 선언: cout, cin 대신 printf, scanf 사용.
MOD=10007MAXN=52factorial=[1]*(MAXN+1)inv_factorial=[1]*(MAXN+1)# 모듈러 거듭제곱 함수defmod_pow(x,y):result=1x%=MODwhiley>0:ify%2==1:result=result*x%MODx=x*x%MODy//=2returnresult# 모듈러 역원 계산defmod_inv(n):returnmod_pow(n,MOD-2)# 이항 계수 계산defnCr(n,r):ifr<0orr>n:return0returnfactorial[n]*inv_factorial[r]%MOD*inv_factorial[n-r]%MODN=int(input())# 팩토리얼과 역팩토리얼 미리 계산foriinrange(1,MAXN+1):factorial[i]=factorial[i-1]*i%MODinv_factorial[MAXN]=mod_inv(factorial[MAXN])foriinrange(MAXN-1,-1,-1):inv_factorial[i]=inv_factorial[i+1]*(i+1)%MODresult=0maxK=N//4ifmaxK>13:maxK=13forkinrange(1,maxK+1):sign=1ifk%2==1else-1# 포함 배제의 부호 결정comb1=nCr(13,k)# 포카드로 사용할 숫자 k개 선택remaining_cards=52-4*k# 남은 카드 수remaining_draws=N-4*k# 남은 뽑아야 할 카드 수ifremaining_draws<0orremaining_draws>remaining_cards:continuecomb2=nCr(remaining_cards,remaining_draws)# 남은 카드에서 선택term=sign*comb1%MOD*comb2%MODresult=(result+term)%MODprint(result)
코드 설명
팩토리얼 및 역팩토리얼 계산: 리스트를 이용하여 미리 계산한다.
mod_pow 함수: 빠른 거듭제곱을 위해 반복문을 사용한다.
포함 배제 원리 적용: C++ 코드와 동일한 로직을 Python으로 구현하였다.
음수 처리 필요 없음: Python의 모듈러 연산은 음수를 자동으로 양수로 변환한다.
결론
이 문제는 포함 배제의 원리를 이용한 조합론 문제로, 정확한 경우의 수 계산이 핵심이다. 모듈러 연산을 처리하기 위해 빠른 거듭제곱과 모듈러 역원을 사용하였다. 이를 통해 시간 복잡도를 효율적으로 관리할 수 있었다. 추가로, 이항 계수를 미리 계산하여 계산 시간을 단축하였다.
이번 문제를 통해 조합론과 포함 배제의 원리를 복습할 수 있었으며, 모듈러 연산에서의 주의점도 다시 한번 상기할 수 있었다. 앞으로도 다양한 문제에 이러한 수학적 아이디어를 적용해보고자 한다.