2022. 1. 28. 11:44ㆍ수학
이전에 조합을 소수의 kk제곱으로 나눈 나머지를 구하는 방법을 소개했다. 그 방법은 너무 어렵기 때문에 조금 더 쉬운 방법을 소개한다.
- Notation
(u!)p(u!)p는 u!u!에서 pp의 배수인 것을 제외한 곱을 의미한다.
소수 pp와 자연수 kk에 대하여 p2kp2k으로 나눈 나머지를 구한다고 하자. 이때 다음 식을 이용한다.
(dpk!)p((d−1)pk!)p≡(pk!)p(modp2k)(dpk!)p((d−1)pk!)p≡(pk!)p(modp2k)
풀어서 써보면 다음과 같다. pkpk와 서로소인 pkpk이하의 자연수들의 집합을 {S}{S}라고 하자.
∏a∈S(dpk+a)≡∏a∈Sa(modp2k)∏a∈S(dpk+a)≡∏a∈Sa(modp2k)
이것을 증명해보자! 좌변의 식을 풀면 다음과 같이 쓸 수 있다.
p2k(...)+dpk(∏a∈Sa)(∑a∈S1a)+∏a∈Sap2k(...)+dpk(∏a∈Sa)(∑a∈S1a)+∏a∈Sa
∏a∈Sa∏a∈Sa는 전부 pp와 서로소인 수들의 곱이므로 pp로 나누어 떨어질 수 없다. 따라서 합 부분이 pkpk으로 나누어 떨어짐을 보이면 된다. pp가 2보다 큰 경우와 2인 경우를 나누어서 살펴본다.
- p>2p>2
1<a, b∈S<pk−11<a, b∈S<pk−1인 자연수 a,ba,b에 대해 ab≡1(modpk)ab≡1(modpk) 이 성립한다고 하자. 윌슨의 정리의 확장에 의해 이런 방식으로 집합 SS에 있는 원소 xx(1<x∈S<pk−11<x∈S<pk−1)를 묶을 수 있다. 1과 pk−1pk−1은 약간 다르게 표현된다. 이는 추후 설명한다.
∑a∈S1a∑a∈S1a
에서 앞서 설명한 a,ba,b를 선택해서 더해보자.
1a+1b=a+bab≡−(a+b)(modpk)1a+1b=a+bab≡−(a+b)(modpk)
x=1,pk−1x=1,pk−1일 때 x2≡1(modpk)x2≡1(modpk)이므로 1x≡x(modpk)1x≡x(modpk)이다.
따라서 다음이 성립한다.
∑a∈S1a≡−∑a∈Sa(modpk)∑a∈S1a≡−∑a∈Sa(modpk)
=−(1+pk)pk2+(p+pk)pk−12=−p2k−1(p−1)2=−(1+pk)pk2+(p+pk)pk−12=−p2k−1(p−1)2
p−1p−1이 2로 나누어 떨어지므로
∑a∈S1a≡∑a∈Sa≡0(modpk)∑a∈S1a≡∑a∈Sa≡0(modpk)
- p=2p=2
∑a∈S1a≡−∑a∈Sa(mod2k)∑a∈S1a≡−∑a∈Sa(mod2k)
=−(1+2k)2k2+(2+2k)2k−12=−22k−2=−(1+2k)2k2+(2+2k)2k−12=−22k−2
에서 2k−2≥k2k−2≥k, k≥2k≥2를 얻는다. 따라서
∑a∈S1a≡−∑a∈Sa≡0(mod2k)∑a∈S1a≡−∑a∈Sa≡0(mod2k)
p=2p=2인 경우 k≥2k≥2임을 주의해서 코드를 짜자.
따라서 다음이 성립한다.
∏a∈S(dpk+a)≡∏a∈Sa(modp2k)∏a∈S(dpk+a)≡∏a∈Sa(modp2k)
이제 이 식을 이용해서 (N!)p(modp2k)(N!)p(modp2k)를 계산할 것이다. 알고리즘은 다음과 같다.
1. pkpk이하의 pkpk과 서로소인 수들의 곱ss과 s2, ..., spks2, ..., spk을 전처리한다.
2. NN을 p2kp2k로 나눈 나머지를 tt라고 하자. 답에 st/pkst/pk를 곱한다. -1을 아래에 있는 설명을 참고해 적절히 곱한다.
3. NN을 pp로 나눈 후 2번 과정을 NN이 0이 될 때까지 반복한다.
이 알고리즘의 작동 원리를 설명하겠다.
NN을 p2kp2k로 나누는 이유를 알아보자. pkpk과 pkpk이하이고 pkpk과 서로소인 수들의 곱은 ±1±1과 합동임을 윌슨의 정리의 확장을 통해 알고 있다. ±±은 p=2p=2일 때 ++이고 나머지는 −−이다. NN이라는 수를 p2kp2k 단위로 나누어 생각해보면 아래와 같다.
(N!)p=[1×...×(p2k−1)][(p2k+1)×...×(2p2k−1)]...[(up2k+1)×...×(up2k+t)](N!)p=[1×...×(p2k−1)][(p2k+1)×...×(2p2k−1)]...[(up2k+1)×...×(up2k+t)]
≡(±1)u[1×...×t](modp2k)≡(±1)u[1×...×t](modp2k)
1부터 t까지의 수들 중 pk으로 묶이는 수들의 곱은 전처리 되어있는 상태이다. 또한 앞서 증명한 식에 의해 다음과 같이 쓸 수 있다.
≡(±1)u[1×...×(pk−1)][(pk+1)×...×(2pk−1)]...[(vpk+1)×...×(vpk+r)](modp2k)
≡(±1)usv[(vpk+1)×...×(vpk+r)](modp2k)
[(vpk+1)×...×(vpk+r)]이 부분은 따로 계산을 해주면 된다.
조합 식을 변형하자. vp(n)을 pk|n을 만족하는 최대의 k로 정의하자.
(nm)=pvp(n)−vp(m)−vp(n−m)(n!)p(m!)p((n−m)!)p
분모에 있는 수들은 어차피 p와 서로소이기 때문에 모듈러 역원을 구해 곱해주면 된다.
이것을 구현한 코드이다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
#include<stdio.h>
#include<algorithm>
#include<utility>
#define MAX 1000001
using namespace std;
typedef long long ll;
typedef pair<ll, ll> Pair;
ll fpow(ll n, ll k, ll mod = 0){
ll s = 1;
for(;k;k >>= 1){
if(mod){
if(k & 1) s = (__int128) s * n % mod;
n = (__int128) n * n % mod;
}
else{
if(k & 1) s = s * n;
n = n * n;
}
}
return s;
}
ll power[MAX] = {1};
Pair factorial_p(ll n, ll p, ll pq, ll bpq){
ll ret = 1, pt = 0, t, i;
while(n){
t = n % pq;
if(p != 2 && (n / pq) % 2) ret = pq - ret;
ret = (__int128) ret * power[t / bpq] % pq;
for(i = t / bpq * bpq + 1;i <= t;i++){
if(i % p == 0) continue;
ret = (__int128) ret * i % pq;
}
pt += n / p;
n /= p;
}
return make_pair(ret, pt);
}
ll bincoeff_pq(ll n, ll m, int p, int q){
ll real = fpow(p, q);
if(q % 2) q += 1; //match with even number
ll pq = fpow(p, q), bpq = fpow(p, q / 2);
if(p == 2){
pq = fpow(p, max(4, q));
bpq = fpow(p, max(4, q) / 2);
}
ll gop = 1, i;
Pair get;
ll up, dn, pt, ans;
for(i = 1;i < bpq;i++){
if(i % p == 0) continue;
gop = (__int128) gop * i % pq;
}
for(i = 1;i <= bpq;i++){
power[i] = (__int128) power[i - 1] * gop % pq;
}
get = factorial_p(n, p, pq, bpq);
up = get.first; pt = get.second;
get = factorial_p(m, p, pq, bpq);
dn = get.first; pt -= get.second;
get = factorial_p(n - m, p, pq, bpq);
dn = (__int128) dn * get.first % pq; pt -= get.second;
if(pt >= q) return 0;
ans = (__int128) up * fpow(dn, pq / p * (p - 1) - 1, pq) % pq;
return (__int128) ans * fpow(p, pt) % real;
}
int main(){
ll n, m;
int p, q;
scanf("%lld %lld %d %d", &n, &m, &p, &q);
printf("%lld", bincoeff_pq(n, m, p, q));
}
|
cs |
'수학' 카테고리의 다른 글
Lucy-Hedgehog Algorithm (0) | 2022.09.18 |
---|---|
Extension of Wilson's Theorem and its application (12) | 2022.07.14 |
Mobius Inversion (0) | 2022.01.23 |
조합 더하기 (0) | 2021.12.20 |
중복 조합 (0) | 2021.11.07 |