Processing math: 8%

Modulo Prime Power Trick

2022. 1. 28. 11:44수학

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

 

- Notation

(u!)pu!에서 p의 배수인 것을 제외한 곱을 의미한다.

 

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

\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 kk \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}과 서로소인 수들의 곱ss^{2},\ ...,\ s^{p^{k}}을 전처리한다. 

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

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

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

Np^{2k}로 나누는 이유를 알아보자. p^{k}p^{k}이하이고 p^{k}과 서로소인 수들의 곱은 \pm 1과 합동임을 윌슨의 정리의 확장을 통해 알고 있다. \pmp=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