P3807 【模板】卢卡斯定理/Lucas 定理

原题链接

简要题意:\(T\) 组数据,每次给定 \(n,m,p\) ,求 \(C_{n+m}^m \mod p\) 的值。

\(T \leq 10 , 1 \leq n,m,p \leq 10^5\) 且保证 \(p\) 为素数。

其实这道题目不需要用 lucas 定理来做,同样可以通过。lucas 可以做到 \(\mathcal{O}(\log_n p * p)\),那如果我们什么都不知道,能做到何种程度呢?

\[C_{n+m}^m = \frac{(n+m)!}{n! \cdot m!} \]

这是我们首先想到的。

考虑 \(p\) 为素数,我们可以预处理阶乘部分,对于 \(n! , m!\) 我们可以求逆元。逆元可以线性预处理。

但是注意到,\(n>p\) 或者 \(m>p\) 的时候,分母似乎没有逆元啊?

请注意到这个分数是一个整数,所以我们 一定可以 将分子与分母所有 \(p\) 的倍数全部约掉。如果没有约掉,说明答案为 \(0\),这是显然的。

好,那么怎么约?其实暴力扫一遍我们就能很好的解决问题(虽然我们应该并不需要)。

暴力的做法就是,直接 \(1 \rightarrow n\) 扫过去,把 \(p\) 的幂次计出来,除剩下的数乘一起。

这个时间复杂度是 \(\mathcal(O)(\log_n p * n + p)\).

但实际上,统计 \(n!\)\(p\) 的幂次是可以做到更优复杂度的。这里笔者未实现,但是做法是有的。

观察到其实 \(p\) 的幂次统计出来并不难,\(\log_n p\) 次就可以完成。

对于其它数的乘积,本质上我们求的是

\[1 \times 2 \times \cdots \times (p-1) \times (p+1) \times (p+2) \times \cdots \times (2p-1) \times \cdots \times n \]

\(n!\) 中扣掉 \(p\) 的倍数,剩下的数是个什么情况。其实这是连续 \(\lfloor \frac{n}{p} \rfloor + 1\) 个块,其中除了最后一个块,其余块长均为 \(p-1\).

于是我可以预处理 \(n\) 以内所有数阶乘的逆元,对于每个块 \(kp+1 - k(p+1)-1\),显然答案为 \((k(p+1)-1)! \cdot \text{inv}((kp)!)\).

注意到这玩意儿已经可以 \(\mathcal{O}(n)\) 计算了,即枚举 \(k\) 然后暴力算。

时间复杂度:$\mathcal{O}(\log_n p + n + p).

注:lucas 适用于 \(n,m\) 很大但 \(p\) 很小的情况。暴力做法则对于 \(n,p\) 都有较大限制。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
int T,n,m,p;

const int N=2e5+1;
int inv[N];
ll ans,k;

inline void calc1(int x) {
	while(x % p == 0) x /= p , k++;
	ans = ans * x % p;
}

inline void calc2(int x) {
	while(x % p == 0) x /= p , k--;
	ans = ans * inv[x%p] % p;
}

int main() {
	scanf("%d",&T);
	while(T--) {
		scanf("%d %d %d",&n,&m,&p);
		inv[0] = inv[1] = 1;
		for(int i=2;i<N;i++) 
			inv[i] = (1ll * (p-p/i) * inv[p%i]) % p;
		ans = 1; k = 0;
		for(int i=1;i<=n+m;i++) calc1(i);
		for(int i=1;i<=n;i++) calc2(i);
		for(int i=1;i<=m;i++) calc2(i);
		if(k) puts("0");
		else  printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2021-12-25 22:57  bifanwen  阅读(210)  评论(0编辑  收藏  举报