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)\),那如果我们什么都不知道,能做到何种程度呢?
这是我们首先想到的。
考虑 \(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\) 次就可以完成。
对于其它数的乘积,本质上我们求的是
即 \(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;
}