Modulo Prime Power Trick

2022. 1. 28. 11:44수학

이전에 조합을 소수의 $k$제곱으로 나눈 나머지를 구하는 방법을 소개했다. 그 방법은 너무 어렵기 때문에 조금 더 쉬운 방법을 소개한다.

 

- Notation

$(u!)_{p}$는 $u!$에서 $p$의 배수인 것을 제외한 곱을 의미한다.

 

소수 $p$와 자연수 $k$에 대하여 $p^{2k}$으로 나눈 나머지를 구한다고 하자. 이때 다음 식을 이용한다.

\[\frac{(dp^{k}!)_{p}}{((d-1)p^{k}!)_{p}} \equiv (p^{k}!)_{p} \pmod{p^{2k}}\]

풀어서 써보면 다음과 같다. $p^{k}$와 서로소인 $p^{k}$이하의 자연수들의 집합을 $\{S\}$라고 하자. 

\[\prod_{a \in S} (dp^{k}+a) \equiv \prod_{a \in S} a \pmod{p^{2k}}\]

이것을 증명해보자! 좌변의 식을 풀면 다음과 같이 쓸 수 있다.

\[p^{2k}(...)+dp^{k}\left(\prod_{a \in S} a\right)\left(\sum_{a \in S} \frac{1}{a}\right)+\prod_{a \in S}a \]

$\prod_{a \in S} a$는 전부 $p$와 서로소인 수들의 곱이므로 $p$로 나누어 떨어질 수 없다. 따라서 합 부분이 $p^{k}$으로 나누어 떨어짐을 보이면 된다. $p$가 2보다 큰 경우와 2인 경우를 나누어서 살펴본다.

 

  • $p>2$

$1<a,\ b \in S<p^{k}-1$인 자연수 $a,b$에 대해 $ab \equiv 1 \pmod{p^{k}}$ 이 성립한다고 하자. 윌슨의 정리의 확장에 의해 이런 방식으로 집합 $S$에 있는 원소 $x$($1<x \in S<p^{k}-1$)를 묶을 수 있다. 1과 $p^{k}-1$은 약간 다르게 표현된다. 이는 추후 설명한다.

\[\sum_{a \in S} \frac{1}{a}\]

에서 앞서 설명한 $a,b$를 선택해서 더해보자.

\[\frac{1}{a}+\frac{1}{b}=\frac{a+b}{ab} \equiv -(a+b) \pmod{p^{k}}\]

$x=1, p^{k}-1$일 때 $x^{2} \equiv 1 \pmod{p^{k}}$이므로 $\frac{1}{x} \equiv x \pmod{p^{k}}$이다. 

따라서 다음이 성립한다.

\[\sum_{a \in S} \frac{1}{a} \equiv -\sum_{a \in S} a \pmod{p^{k}}\]

\[=-\frac{(1+p^{k})p^{k}}{2}+\frac{(p+p^{k})p^{k-1}}{2}=-\frac{p^{2k-1}(p-1)}{2}\]

$p-1$이 2로 나누어 떨어지므로 

\[\sum_{a \in S} \frac{1}{a} \equiv \sum_{a \in S} a \equiv 0 \pmod{p^{k}}\]

 

  • $p=2$

\[\sum_{a \in S} \frac{1}{a} \equiv -\sum_{a \in S} a \pmod{2^{k}}\]

\[=-\frac{(1+2^{k})2^{k}}{2}+\frac{(2+2^{k})2^{k-1}}{2}=-2^{2k-2}\]

에서 $2k-2 \ge k$,  $k \ge 2$를 얻는다. 따라서

\[\sum_{a \in S} \frac{1}{a} \equiv -\sum_{a \in S} a \equiv 0 \pmod{2^{k}}\]

$p=2$인 경우 $k \ge 2$임을 주의해서 코드를 짜자.

 

따라서 다음이 성립한다.

\[\prod_{a \in S} (dp^{k}+a) \equiv \prod_{a \in S} a \pmod{p^{2k}}\]

 

이제 이 식을 이용해서 $(N!)_{p} \pmod{p^{2k}}$를 계산할 것이다. 알고리즘은 다음과 같다.

1. $p^{k}$이하의 $p^{k}$과 서로소인 수들의 곱$s$과 $s^{2},\ ...,\ s^{p^{k}}$을 전처리한다. 

2. $N$을 $p^{2k}$로 나눈 나머지를 $t$라고 하자. 답에 $s^{t/p^{k}}$를 곱한다. -1을 아래에 있는 설명을 참고해 적절히 곱한다.

3. $N$을 $p$로 나눈 후 2번 과정을 $N$이 0이 될 때까지 반복한다.

이 알고리즘의 작동 원리를 설명하겠다.

$N$을 $p^{2k}$로 나누는 이유를 알아보자. $p^{k}$과 $p^{k}$이하이고 $p^{k}$과 서로소인 수들의 곱은 $\pm 1$과 합동임을 윌슨의 정리의 확장을 통해 알고 있다. $\pm$은 $p=2$일 때 $+$이고 나머지는 $-$이다. $N$이라는 수를 $p^{2k}$ 단위로 나누어 생각해보면 아래와 같다.

\[(N!)_{p}=[1\times...\times(p^{2k}-1)][(p^{2k}+1)\times...\times(2p^{2k}-1)]...[(up^{2k}+1)\times...\times(up^{2k}+t)]\]

\[\equiv (\pm 1)^{u}[1\times...\times t]\pmod{p^{2k}}\]

1부터 $t$까지의 수들 중 $p^{k}$으로 묶이는 수들의 곱은 전처리 되어있는 상태이다. 또한 앞서 증명한 식에 의해 다음과 같이 쓸 수 있다.

\[\equiv (\pm 1)^{u}[1\times...\times (p^{k}-1)][(p^{k}+1)\times...\times (2p^{k}-1)]...[(v p^{k}+1)\times...\times (vp^{k}+r)]\pmod{p^{2k}}\]

\[\equiv (\pm 1)^{u}s^{v}[(vp^{k}+1)\times...\times (vp^{k}+r)]\pmod{p^{2k}}\]

$[(vp^{k}+1)\times...\times (vp^{k}+r)]$이 부분은 따로 계산을 해주면 된다.

 

조합 식을 변형하자. $v_{p}(n)$을 $p^{k} |n$을 만족하는 최대의 $k$로 정의하자.

 \[{n \choose m}=p^{v_{p}(n)-v_{p}(m)-v_{p}(n-m)} \frac{(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 == 0continue;
            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 == 0continue;
        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