【P4720】 扩展卢卡斯

要注意的点还是很多的比如求n! mod (p^k)时, 对剩余不足一周期的部分(即计算remain时)i可能很大, remain * i很容易爆long long,注意要取模。

#include <algorithm>
#include <iostream>

using namespace std;

using ll = long long;

ll gcd(ll a, ll b){
    return b == 0? a: gcd(b, a % b);
}
ll exgcd(ll a, ll b, ll & x, ll & y){
    if (!b){
        x = 1, y = 0;
        return a;
    }
    ll r = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return r;
}

// (a, mod) = 1
ll inv(int a, int mod){
    ll x, y;
    exgcd(a, mod, x, y);
    return (x + mod) % mod;
}

ll qpow(ll a, ll b, ll mod){
    ll res = 1;
    a %= mod;
    while (b){
        if (b & 1ll) res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}

// cal  n!/p^x mod(p^k)
// get x separately later
ll F(ll n, ll p, ll pk){
    if (n == 0) return 1;
    ll cy = 1, remain = 1;
    for (ll i = 1; i <= pk; ++i) {
        if (i % p) cy = (cy * i) % pk;
    }
    cy = qpow(cy, n / pk, pk);
    for (ll i = (pk * (n / pk)); i <= n; ++i) {
        // i % pk
        if (i % p) remain = remain * (i % pk) % pk;
    }
    return F(n / p, p, pk) * cy % pk * remain % pk;
}

// count x in n! / p^x
ll count(ll n, ll p){
    if (n < p) return 0;
    return count(n / p, p) + n / p;
}

ll C_pk(ll n, ll m, ll p, ll pk){
    ll u = F(n, p, pk);
    ll v = inv(F(m, p, pk), pk);
    ll w = inv(F(n - m, p, pk), pk);
    ll exp = qpow(p, count(n, p) - count(m, p) - count(n - m, p), pk);
    return u * v % pk * w % pk * exp % pk;
}

ll A[1001], B[1001];
// x = b(mod a)

ll exLucas(ll n, ll m, ll p){
    int tot = 0;
    ll p2 = p;
    for (ll div = 2; div * div <= p; ++div) {
        if (p2 % div == 0){
            ll pk = 1;
            while (p2 % div == 0){
                p2 /= div;
                pk *= div;
            }
            A[++tot] = pk;
            B[tot] = C_pk(n, m, div, pk);
        }
    }
    if (p2 > 1){
        A[++tot] = p2;
        B[tot] = C_pk(n, m, p2, p2);
    }
    //A[i] is co-prime
    // use CRT to find the minial x
    ll res = 0;
    for (int i = 1; i <= tot; ++i) {
        ll M = p / A[i];
        res = (res + B[i] * M % p * inv(M, A[i]) % p) % p;
    }
    return res;
}


#define debug

int main(){
#ifdef debug
    freopen("in.txt", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    ll n, m, p;
    cin >> n >> m >> p;
    cout << exLucas(n, m, p) << '\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值