HDU3037 Saving Beans(Lucas定理+乘法逆元)
题目大概问小于等于m个的物品放到n个地方有几种方法。
即解这个n元一次方程的非负整数解的个数$x_1+x_2+x_3+\dots+x_n=y$,其中0<=y<=m。
这个方程的非负整数解个数是个经典问题,可以+1转化正整数解的个数用插板法解决:$C_{y+n-1}^{n-1}=C_{y+n-1}^y$。
而0<=y<=m,最后的结果就是——
$$\sum_{i=0}^m C_{i+n-1}^i$$
$$C_{n-1}^0+C_{n}^1+C_{n+1}^2+\dots+C_{n-1+m}^m$$
$$C_{n}^0+C_{n}^1+C_{n+1}^2+\dots+C_{n-1+m}^m$$
$$C_{n+1}^1+C_{n+1}^2+\dots+C_{n-1+m}^m\ \tag{$C_{n+1}^1=C_{n}^0+C_{n}^1$}$$
$$C_{n+2}^2+\dots+C_{n-1+m}^m\ \tag{$C_{n+2}^2=C_{n+1}^1+C_{n+1}^2$}$$
$$\vdots$$
$$C_{n+m}^m$$
于是就推算出结果是$C_{n+m}^m$。那么就是计算$C_{n+m}^m\ mod \ p$,其中1<=n,m<=1000000000,1<p<100000且p为质数。
这时就是用Lucas定理来计算这种大组合数的模:$Lucas(n,m)\equiv C_{n\%p}^{m\%p}\times Lucas(n/p,m/p)\pmod p$。
另外计算组合数时,利用模p下的乘法逆元,$C_n^m\equiv\frac {n!}{(n-m)!m!}\equiv n!\times((n-m)!m!)^{-1} \pmod p$
而计算逆元没必要用扩展欧几里得算法,因为p是质数,利用费马小定理可以推出n在模p下的乘法逆元为$n^{p-2}\ mod\ p$。
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 long long ine(long long n,long long p){ 5 long long res=1,m=p-2; 6 while(m){ 7 if(m&1) res=res*n%p; 8 n=n*n%p; 9 m>>=1; 10 } 11 return res; 12 } 13 long long fact[100000]={1}; 14 long long lucas(long long n,long long m,long long p){ 15 long long res=1; 16 while(n&&m){ 17 long long a=n%p,b=m%p; 18 if(a<b) return 0; 19 res=res*fact[a]*ine(fact[b]*fact[a-b]%p,p)%p; 20 n/=p; m/=p; 21 } 22 return res; 23 } 24 int main(){ 25 long long n,m,p; 26 int t; 27 scanf("%d",&t); 28 while(t--){ 29 scanf("%lld%lld%lld",&n,&m,&p); 30 for(int i=1; i<p; ++i) fact[i]=fact[i-1]*i%p; 31 printf("%lld\n",lucas(n+m,m,p)); 32 } 33 return 0; 34 }