题目地址:
https://www.acwing.com/problem/content/889/
给定 n n n组询问,每次询问给定三个正整数 a a a, b b b和 p p p,其中 p p p是素数。求 ( a b ) m o d p {a\choose b}\mod p (ba)modp。
数据范围:
1 ≤ n ≤ 20 1\le n\le 20 1≤n≤20
1 ≤ b ≤ a ≤ 1 0 18 1\le b\le a\le 10^{18} 1≤b≤a≤1018
1 ≤ p ≤ 1 0 5 1\le p\le 10^5 1≤p≤105
先介绍Lucas定理,对于非负整数 m m m, n n n和素数 p p p,有: ( m n ) ≡ ( m m o d p n m o d p ) ( m / p n / p ) ( m o d p ) {m \choose n}\equiv {m\mod p\choose n\mod p}{m/p\choose n/p}(\mod p) (nm)≡(nmodpmmodp)(n/pm/p)(modp)换言之,有: ( m n ) ≡ ∏ i = 0 k ( m i n i ) ( m o d p ) {m \choose n}\equiv \prod_{i=0}^{k}{m_i \choose n_i}(\mod p) (nm)≡i=0∏k(nimi)(modp)其中 m = m k p k + m k − 1 p k − 1 + . . . + m 1 p + m 0 m=m_kp^k+m_{k-1}p^{k-1}+...+m_1p+m_0 m=mkpk+mk−1pk−1+...+m1p+m0, n = n k p k + n k − 1 p k − 1 + . . . + n 1 p + n 0 n=n_kp^k+n_{k-1}p^{k-1}+...+n_1p+n_0 n=nkpk+nk−1pk−1+...+n1p+n0即把 m m m和 n n n写成 p p p进制整数。证明如下,由于 p p p是素数,所以 ( 1 + x ) p k ≡ 1 + x p k ( m o d p ) (1+x)^{p^k}\equiv 1+x^{p^k}(\mod p) (1+x)pk≡1+xpk(modp)。考虑 ( 1 + x ) m (1+x)^m (1+x)m,有: ( 1 + x ) m k p k + m k − 1 p k − 1 + . . . + m 1 p + m 0 = ( ( 1 + x ) p k ) m k ( ( 1 + x ) p k − 1 ) m k − 1 . . . ( ( 1 + x ) p ) m 1 ( 1 + x ) m 0 ≡ ( 1 + x p k ) m k ( 1 + x p k − 1 ) m k − 1 . . . ( 1 + x p ) m 1 ( 1 + x ) m 0 ( m o d p ) (1+x)^{m_kp^k+m_{k-1}p^{k-1}+...+m_1p+m_0}\\=((1+x)^{p^k})^{m_k}((1+x)^{p^{k-1}})^{m_{k-1}}...((1+x)^p)^{m_1}(1+x)^{m_0}\\\equiv (1+x^{p^k})^{m_k}(1+x^{p^{k-1}})^{m_{k-1}}...(1+x^p)^{m_1}(1+x)^{m_0}(\mod p) (1+x)mkpk+mk−1pk−1+...+m1p+m0=((1+x)pk)mk((1+x)pk−1)mk−1...((1+x)p)m1(1+x)m0≡(1+xpk)mk(1+xpk−1)mk−1...(1+xp)m1(1+x)m0(modp)考虑 x n x^n xn的系数,有: ( m n ) ≡ ∏ i = 0 k ( m i n i ) ( m o d p ) {m\choose n}\equiv \prod_{i=0}^{k}{m_i \choose n_i}(\mod p) (nm)≡i=0∏k(nimi)(modp) ≡ \equiv ≡左边是显然的,其右边,由于当 k ≥ p k\ge p k≥p的时候有 ( m k ) ≡ 0 ( m o d p ) {m\choose k}\equiv 0(\mod p) (km)≡0(modp),所以可以只考虑 k < p k<p k<p的时候的系数。
接下来只需要套用上面的公式即可,要求 ( m n ) m\choose n (nm),对于 ( m m o d p n m o d p ) {m\mod p\choose n\mod p} (nmodpmmodp)可以直接求,而对于 ( m / p n / p ) {m/p\choose n/p} (n/pm/p)可以递归地去求。代码如下:
#include <iostream>
using namespace std;
int p;
// 快速幂
int fast_pow(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = (long) res * a % p;
a = (long) a * a % p;
k >>= 1;
}
return res;
}
int C(int a, int b) {
int res = 1;
// 对于res,从a向下乘b步,然后除以b阶乘(乘以其逆元)
for (int i = 1, j = a; i <= b; i++, j--) {
res = (long) res * j % p;
res = (long) res * fast_pow(i, p - 2) % p;
}
return res;
}
int lucas(long a, long b) {
if (a < p && b < p) return C(a, b);
return (long) C(a % p, b % p) * lucas(a / p, b / p) % p;
}
int main() {
int n;
cin >> n;
while (n--) {
long a, b;
cin >> a >> b >> p;
cout << lucas(a, b) << endl;
}
return 0;
}
时间复杂度 O ( log p N N log p ) = O ( N log N ) O(\log_pN N\log p)=O(N\log N) O(logpNNlogp)=O(NlogN),空间 O ( log p N ) O(\log_pN) O(logpN), N N N是输入范围。