P3807 【模板】卢卡斯定理

P3807 【模板】卢卡斯定理

\(C_{m + n}^{m} \% p\) ( \(1\le n,m,p\le 10^5\) )


错误日志: 数组开小(哇啊啊啊洼地hi阿偶我姑父阿贺佛奥UFO爱我帮你)


Pre

好的我们继续恶补数学
首先复习一下 \(O(N)\) 求质数逆元的方法$$inv[1] = 1$$$$inv[i] = (p - p / i) * inv[p % i] % p (i >= 2)$$

LL inv[maxn];
void get_inv(LL n){
	inv[1] = 1;
	for(LL i = 2;i <= n;i++)inv[i] = (p - p / i) * inv[p % i] % p;
	}

然后是 \(O(m)\)\(C_{n}^{m} \% p\):$$C_{n}^{m}% p = \frac{n!}{m!(n - m)!}% p$$$$=\frac{(n - m + 1) * (n - m +2) * ... * n}{m!}% p$$$$=(\frac{n - m + 1}{1}% p) * (\frac{n - m + 2}{2}% p) * ... * (\frac{n}{m}% p)$$
其中除法取模可以用上面的逆元计算, 求解一个组合数的复杂度为 \(O(m)\)

LL C(LL n, LL m){
	LL ans = 1;
	for(LL i = 1;i <= m;i++)ans = ans * (n - m + i) * inv[i] % p;
	return ans;
	}

最后就是卢卡斯定理, 当 \(p\) 为质数时有:$$C_{n}^{m} % p = C_{n % p}^{m % p} * C_{n / p}^{m / p} % p$$
其中取模过了的部分可以很快的计算出来, 另一部分继续递归卢卡斯即可

LL lucas(LL n, LL m, LL p){
	if(m == 0)return 1;
	return C(n % p, m % p) * lucas(n / p, m / p, p) % p;
	}

Solution

于是乎掌握了上边的知识后就变成裸题啦

Code

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#include<climits>
#define LL long long
using namespace std;
LL RD(){
    LL out = 0,flag = 1;char c = getchar();
    while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
    while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
    return flag * out;
    }
const LL maxn = 200019;
LL n, m, p;
LL inv[maxn];
void get_inv(LL n){
	inv[1] = 1;
	for(LL i = 2;i <= n;i++)inv[i] = (p - p / i) * inv[p % i] % p;
	}
LL C(LL n, LL m){
	LL ans = 1;
	for(LL i = 1;i <= m;i++)ans = ans * (n - m + i) * inv[i] % p;
	return ans;
	}
LL lucas(LL n, LL m, LL p){
	if(m == 0)return 1;
	return C(n % p, m % p) * lucas(n / p, m / p, p) % p;
	}
int main(){
	LL T = RD();
	while(T--){
		n = RD(), m = RD(), p = RD();
		get_inv(m);
		printf("%lld\n", lucas(n + m, m, p));
		}
	return 0;
	}
posted @ 2018-08-24 21:54  Tony_Double_Sky  阅读(188)  评论(0编辑  收藏  举报