前前言

很久之前学的了,但一直没有机会用到,就写个 防止忘记吧。

题意


其中, 不一定为质数。
模板题在这里!

前言

定理 和 定理一点关系都没有。。。。
所以根本不需要你会 定理,不过你得会中国剩余定理QAQ
好了下面进入正题!!

主要思路

  1. 先将 分解质因数,变为
  2. 分别求出
  3. 用中国剩余定理合并

分析

现在的主要问题就是求
但是 实在是太太太大了,让这一切非常难顶。
为了让下面存在逆元,我们要把所有的 提出来。
,其中 ,也就是把所有 的次数都提出来。
于是原式可以写成这种形式:

现在的问题就变成:

  1. 求出
  2. 求出 ,然后求波逆元就完事儿了

第一个问题很好求嘛,令 表示 的次数,有以下递推式:

我们重点看第二个问题!!

第二个问题

我们要快速求出
我们把 分成两部分,非 的倍数和 的倍数。

的倍数

现在我们把所有 的倍数拿掉,大概是这个样子:
我们把每 个数放到一个中括号里,称这个为一组。其中,第 组为
考虑 这个数,它是 组的最后一个数。
那么它的下一组,也就是第 组,就是
而这一组,在模 意义下,和第一组一模一样!!
也就是
这说明,出现了循环节!!
那我们把每 组放在一节,乘积记作
那么总共有 节,这部分乘积为
最后剩的部分的数不超过 个,我们暴力求即可。
于是,我们就在 时间内求出了非 的倍数的乘积,记作

的倍数

考虑
我们去掉所有 ,就变成
接下来我们就是要求 。这部分递归求就可以了。

综上


复杂度是

最后用中国剩余定理合并即可。

拓展中国剩余定理

考虑到可能有人不会 ,而且我怕自己忘了,就在这里把 介绍一下吧。
下面要求以下线性同余方程组的一个解:

其中, 不一定两两互质。

考虑我们已经求出了前 个方程的一个解 ,现在求前 个方程的一个解。

那么前 个方程的所有解为
考虑某个解满足第 个方程,即:
我们的目标是求出某个满足的 ,移一下项,有:
,如果 ,那么方程组无解。
否则,方程等价为:
其中
于是有 关于 的逆元存在,于是可以求出某个解 ,即:
于是我们可以在 时间内求出线性同余方程组的解。

代码超级无敌清晰!

总代码如下

#include <bits/stdc++.h>
#define m_p make_pair
#define N 100005
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;

LL z = 1;
LL read(){
    LL x, f = 1;
    char ch;
    while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1;
    x = ch - '0';
    while(ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + ch - 48;
    return x * f;
}

LL ksm(LL a, LL b, LL p){
    LL s = 1;
    while(b){
        if(b & 1) s = z * s * a % p;
        a = z * a * a % p;
        b >>= 1;
    }
    return s;
}

vector<pair<LL, LL> > d;

void get(LL x){//分解质因数,得到 p 和 p^k
    for(LL i = 2; i <= x / i; i++){
        if(x % i == 0){
            LL tot = 1;
            while(x % i == 0) tot *= i, x /= i;
            d.push_back(m_p(i, tot));
        }
    }
    if(x > 1) d.push_back(m_p(x, x));
}

LL f(LL n, LL p, LL pk){//求 f(n) 
    if(n == 0 || n == 1) return 1;
    LL i, s = 1;
    for(i = 1; i <= pk; i++) if(i % p) s = s * i % pk;
    s = ksm(s, n / pk, pk);
    for(i = n / pk * pk; i <= n; i++) if(i % p) s = s * (i % pk) % pk;
    return s * f(n / p, p, pk) % pk;
}

LL g(LL n, LL p){//求 g(n) 
    if(n < p) return 0;
    return n / p + g(n / p, p);
}

void exgcd(LL a, LL b, LL &x, LL &y){
    if(!b) x = 1, y = 0;
    else{
        exgcd(b, a % b, y, x);
        y -= a / b * x;
    }
}

LL inv(LL a, LL b){
    LL x, y;
    exgcd(a, b, x, y);
    return (x + b) % b;//求逆元 
}

LL excrt(LL n, LL *a, LL *b){//excrt 合并解 
    LL x, B, M, t, k, g;
    __int128 z = 1;
    x = a[1], B = b[1];
    for(LL i = 2; i <= n; i++){
        g = __gcd(B, b[i]);
        M = B / g * b[i];
        if((a[i] - x) % g != 0) return -1;
        exgcd(B / g, b[i] / g, t, k);
        t = (z * t * (a[i] - x) / g % M + M) % M;
        x = (x + z * t * B % M) % M;
        B = M;
    }
    return x;
}

LL a[N], b[N];

LL exLucas(LL n, LL m, LL p){
    LL ans = 1, i, j, k, pk, w, cnt = 0;
    get(p);
    for(auto t: d){
        p = t.first, pk = t.second;
        i = f(n, p, pk);
        j = inv(f(m, p, pk), pk); k = inv(f(n - m, p, pk), pk);
        w = g(n, p) - g(m, p) - g(n - m, p);
        i = i * j % pk * k % pk;
        i = i * ksm(p, w, pk) % pk;
        a[++cnt] = i, b[cnt] = pk;//得到 C(n, m) % p^k 
    }
    return excrt(cnt, a, b);
}

int main(){
    int i, j;
    LL n, m, p;
    n = read(); m = read(); p = read();
    printf("%lld", exLucas(n, m, p));
    return 0;
}