题目描述:
给定n组询问,每组询问给定三个整数a,b,p,其中p是质数,请你输出C(a,b) mod p的值。
输入格式
第一行包含整数n。接下来n行,每行包含一组a,b,p。
输出格式
共n行,每行输出一个询问的解。
数据范围
1≤n≤20,1≤b≤a≤10^18,1≤p≤10^5,
输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2
分析:
本题a和b的值都比较大,可以使用卢卡斯定理来做。其实我们只需要记住一个结论:C(a,b) mod p = C(a mod p,b mod p) * C(a / p,b / p) mod p即可解决本题。也可以偷懒不去看卢卡斯定理的证明,因为看了也不一定看得懂。但是本着求真务实的精神,还是证一下吧。先把最常规的证明贴上来:
下面要做的是解释上面图片的推导过程,首先看见a和b都被展开成一个质数p的多项式可能有点懵,其实只需要理解为p进制数即可,举个例子,a = 107,p = 7,107 = 107 % 7 + 107 / 7 * 7 = 2 + 105 = 2 + 7 + 49 * 2 = 2 * 7^0 + 1 * 7^1 + 2 * 7^2.我们先假设卢卡斯定理的这个结论:成立,C(a,b) mod p = C(a mod p,b mod p) * C(a / p,b / p) mod p相当于之前结论的递推式,就像107固然可以展开成2 + 7 + 49*2,也可以展开成107 % 7 + 107 / 7 * 7一样。
然后是的结论,理解为C(p,j)中肯定包含p这个因子,因此模上p为0,。由于展开式中间都含有因子p,故模上p后留下的只有1和x^p,这个式子是将a展开成p进制多项式后展开,调用之前的结论得出的。下面的最后一步,也是关键,左边x^b的系数为C(a,b),(1+x)^a0中x^b0的系数为C(a0,b0),...,(1+x^(p^k))^ak中x^bkp^k的系数为C(ak,bk),故C(a0,b0)x^b0 * ... * C(ak,bk)x^bkp^k = C(a0,b0)* ... * C(ak,bk)x^(b0+...+bkp^k) =C(a0,b0)* ... * C(ak,bk)x^b。故 。如果右边某项bi > ai说明C(a,b) = 0,右边无法凑出x^k的系数。
下面正式运用卢卡斯定理解决本题。C(a,b) mod p = C(a mod p,b mod p) * C(a / p,b / p) mod p,a、b都小于p时,直接计算C(a,b),否则递归的调用卢卡斯定理。具体的C(a,b)= a * (a - 1) *... (a - b + 1) / b!,除法运算同样改为乘上乘法逆元的操作。
复杂度分析:求C(a,b)操作耗时是p的字长log10^5约等于60,lucas递归函数的复杂度为logpa * log2p * n,a最大取10^18约等于2^60,p = 2时,logpa约等于60,n最大取20,一共是1200,p = 10^5时,logpa约等于4,一共是4800,当然p取中间某数时执行次数可能超过1w,但是肯定可以在1s内完成。
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
int qmi(int a,int k,int p){
int res = 1;
while(k){
if(k & 1) res = (ll)res * a % p;
a = (ll)a * a % p;
k >>= 1;
}
return res;
}
int C(int a,int b,int p){
if(b > a) return 0;
int res = 1;
for(int i = 1,j = a;i <= b;i++,j--){
res = (ll)res * j % p;
res = (ll)res * qmi(i,p - 2,p) % p;
}
return res;
}
int lucas(ll a,ll b,int p){
if(a < p && b < p) return C(a,b,p);
return (ll)C(a % p,b % p,p) * lucas(a / p,b / p,p) % p;
}
int main(){
int n,p;
ll a,b;
cin>>n;
while(n--){
cin>>a>>b>>p;
cout<<lucas(a,b,p)<<endl;
}
return 0;
}