Modulo Prime Power Trick

2022. 1. 28. 11:44수학

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

 

- Notation

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

 

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

(dpk!)p((d1)pk!)p(pk!)p(modp2k)(dpk!)p((d1)pk!)p(pk!)p(modp2k)

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

aS(dpk+a)aSa(modp2k)aS(dpk+a)aSa(modp2k)

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

p2k(...)+dpk(aSa)(aS1a)+aSap2k(...)+dpk(aSa)(aS1a)+aSa

aSaaSa는 전부 pp와 서로소인 수들의 곱이므로 pp로 나누어 떨어질 수 없다. 따라서 합 부분이 pkpk으로 나누어 떨어짐을 보이면 된다. pp가 2보다 큰 경우와 2인 경우를 나누어서 살펴본다.

 

  • p>2p>2

1<a, bS<pk11<a, bS<pk1인 자연수 a,ba,b에 대해 ab1(modpk)ab1(modpk) 이 성립한다고 하자. 윌슨의 정리의 확장에 의해 이런 방식으로 집합 SS에 있는 원소 xx(1<xS<pk11<xS<pk1)를 묶을 수 있다. 1과 pk1pk1은 약간 다르게 표현된다. 이는 추후 설명한다.

aS1aaS1a

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

1a+1b=a+bab(a+b)(modpk)1a+1b=a+bab(a+b)(modpk)

x=1,pk1x=1,pk1일 때 x21(modpk)x21(modpk)이므로 1xx(modpk)1xx(modpk)이다. 

따라서 다음이 성립한다.

aS1aaSa(modpk)aS1aaSa(modpk)

=(1+pk)pk2+(p+pk)pk12=p2k1(p1)2=(1+pk)pk2+(p+pk)pk12=p2k1(p1)2

p1p1이 2로 나누어 떨어지므로 

aS1aaSa0(modpk)aS1aaSa0(modpk)

 

  • p=2p=2

aS1aaSa(mod2k)aS1aaSa(mod2k)

=(1+2k)2k2+(2+2k)2k12=22k2=(1+2k)2k2+(2+2k)2k12=22k2

에서 2k2k2k2kk2k2를 얻는다. 따라서

aS1aaSa0(mod2k)aS1aaSa0(mod2k)

p=2p=2인 경우 k2k2임을 주의해서 코드를 짜자.

 

따라서 다음이 성립한다.

aS(dpk+a)aSa(modp2k)aS(dpk+a)aSa(modp2k)

 

이제 이 식을 이용해서 (N!)p(modp2k)(N!)p(modp2k)를 계산할 것이다. 알고리즘은 다음과 같다.

1. pkpk이하의 pkpk과 서로소인 수들의 곱sss2, ..., spks2, ..., spk을 전처리한다. 

2. NNp2kp2k로 나눈 나머지를 tt라고 하자. 답에 st/pkst/pk를 곱한다. -1을 아래에 있는 설명을 참고해 적절히 곱한다.

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

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

NNp2kp2k로 나누는 이유를 알아보자. pkpkpkpk이하이고 pkpk과 서로소인 수들의 곱은 ±1±1과 합동임을 윌슨의 정리의 확장을 통해 알고 있다. ±±p=2p=2일 때 ++이고 나머지는 이다. NN이라는 수를 p2kp2k 단위로 나누어 생각해보면 아래와 같다.

(N!)p=[1×...×(p2k1)][(p2k+1)×...×(2p2k1)]...[(up2k+1)×...×(up2k+t)](N!)p=[1×...×(p2k1)][(p2k+1)×...×(2p2k1)]...[(up2k+1)×...×(up2k+t)]

(±1)u[1×...×t](modp2k)(±1)u[1×...×t](modp2k)

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

(±1)u[1×...×(pk1)][(pk+1)×...×(2pk1)]...[(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(nm)(n!)p(m!)p((nm)!)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