【学习笔记】扩展Lucas定理

280 篇文章 1 订阅
4 篇文章 0 订阅

0.概述

用于解决组合数取模的问题。请先学习中国剩余定理,有点作用的。

1.分解

现在我们要求 ( n m )   m o d   p {n\choose m}\bmod p (mn)modp 的结果, p p p 是个小家伙, n , m n,m n,m 长的很魁梧。

现在我们找到 p p p 的质因数分解 p = ∏ i p i    t i p=\prod_{i}p_i^{\;t_i} p=ipiti

我们如果能求出 ( n m )   m o d   p i    t i {n\choose m}\bmod p_i^{\;t_i} (mn)modpiti

再用中国剩余定理合成回去即可了!所以下文就只讨论对 p k ( p ∈ P ) p^k(p\in\Bbb{P}) pk(pP) 取模的情况。

2.硬算

( n m ) = n ! m ! ( n − m ) ! {n\choose m}=\frac{n!}{m!(n-m)!} (mn)=m!(nm)!n!

我们试着单独考虑 p p p 的次数,就可以求逆元了。用 f ( n ) = n p x   m o d   p k f(n)=\frac{n}{p^x}\bmod p^k f(n)=pxnmodpk 来表示,其中 x x x 是满足 p x ∣ n p^x|n pxn 最大的 x x x(即去除 n n n 的质因数分解的 p p p 那一项)。

我们将 n ! n! n! 分成两部分,一部分是 p p p 的倍数,另一部分不是。即 n ! = ∏ i = 1 ⌊ n p ⌋ ( i p ) ⋅ ∏ ¬ ( p ∣ i ) i ≤ n i n!=\prod_{i=1}^{\lfloor\frac{n}{p}\rfloor}(ip)\cdot \prod_{\neg(p|i)}^{i\le n}i n!=i=1pn(ip)¬(pi)ini

后面那一部分大有搞头。毕竟你要对 p k p^k pk 取模,做乘法之前也可以先行取模。我们根据 “除以 p k p^k pk 的商” 进行分类,就可以看出
∏ ¬ ( p ∣ i ) i ≤ n i ≡ ( ∏ ¬ ( p ∣ i ) i ≤ p k i ) ⌊ n p k ⌋ × ∏ ¬ ( p ∣ i ) i ≤ ( n   m o d   p k ) i ( m o d p k ) \prod_{\neg(p|i)}^{i\le n}i\equiv \left(\prod_{\neg(p|i)}^{i\le p^k}i\right)^{\left\lfloor\frac{n}{p^k}\right\rfloor} \times \prod_{\neg(p|i)}^{i\le(n\bmod p^k)}i\pmod{p^k} ¬(pi)ini¬(pi)ipkipkn׬(pi)i(nmodpk)i(modpk)

然后前面那一坨把 p p p 提出来,得到
∏ i = 1 ⌊ n p ⌋ ( i p ) = p ⌊ n p ⌋ ∏ i = 1 ⌊ n p ⌋ i = p ⌊ n p ⌋ × ⌊ n p ⌋ ! \prod_{i=1}^{\lfloor\frac{n}{p}\rfloor}(ip) =p^{\lfloor\frac{n}{p}\rfloor}\prod_{i=1}^{\lfloor\frac{n}{p}\rfloor}i =p^{\lfloor\frac{n}{p}\rfloor}\times\left\lfloor\frac{n}{p}\right\rfloor\large{!} i=1pn(ip)=ppni=1pni=ppn×pn!

注意到 ⌊ n p ⌋ ! \lfloor\frac{n}{p}\rfloor! pn! 中可能仍然有 p p p 的因数,于是要递归处理。总结一下就是
f ( n ) ≡ f ( ⌊ n p ⌋ ) × ( ∏ ¬ ( p ∣ i ) i ≤ p k i ) ⌊ n p k ⌋ × ∏ ¬ ( p ∣ i ) i ≤ ( n   m o d   p k ) i ( m o d p k ) f(n)\equiv f\left(\middle\lfloor\frac{n}{p}\middle\rfloor\right) \times\left(\prod_{\neg(p|i)}^{i\le p^k}i\right)^{\lfloor\frac{n}{p^k}\rfloor} \times\prod_{\neg(p|i)}^{i\le(n\bmod p^k)}i\pmod{p^k} f(n)f(pn)׬(pi)ipkipkn׬(pi)i(nmodpk)i(modpk)

复杂度呢?如果直接按照这个式子递归,很明显 T ( n ) = T ( ⌊ n p ⌋ ) + O ( p k log ⁡ n ) = O ( p k log ⁡ 2 n ) T(n)=T(\lfloor\frac{n}{p}\rfloor)+\mathcal O(p^k\log n)=\mathcal O(p^k\log^2n) T(n)=T(pn)+O(pklogn)=O(pklog2n) 。如果你再细心一些,你发现 ∏ ¬ ( p ∣ i ) i ≤ p k i \prod_{\neg(p|i)}^{i\le p^k}i ¬(pi)ipki 都是以幂的形式乘在一起。包括 n   m o d   p k n\bmod p^k nmodpk 也唯一决定了最后一项的值。所以你考虑 预处理 x ≤ p k x\le p_k xpk 的前缀积,而后可以 O ( 1 ) \mathcal O(1) O(1) 算后两项的值。所以是 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 单次的。

别指望它是万能的,如果 p = 2 32 p=2^{32} p=232 ,那么复杂度还是 O ( n ) \mathcal O(n) O(n) 的了。中国剩余定理合并的时间是不用考虑的,很小。

p p p 的因数提了出来, p p p 的指数是几呢?众所周知,
max ⁡ x ∈ N + ,    p x ∣ n x = ∑ i = 1 + ∞ ⌊ n p i ⌋ \max_{x\in\N^+,\;p^x|n}x=\sum_{i=1}^{+\infty}\left\lfloor\frac{n}{p^i}\right\rfloor xN+,pxnmaxx=i=1+pin

然后就完了。可以提一下 k = 1 k=1 k=1 的特殊情况, ( n m ) ≡ ( n / p m / p ) ( n   m o d   p m   m o d   p ) ( m o d p ) , p ∈ P {n\choose m}\equiv{n/p\choose m/p}{n\bmod p\choose m\bmod p}\pmod{p},\quad p\in\Bbb{P} (mn)(m/pn/p)(mmodpnmodp)(modp),pP

除法是下取整, P \Bbb P P 是质数集合。

代码

这个代码是洛谷板题的,使用暴力递归法。

inline int qkpow(int_ base,int_ q,int Mod){
	int ans = 1; base %= Mod;
	for(; q; q>>=1,base=1ll*base*base%Mod)
		if(q&1) ans = 1ll*ans*base%Mod;
	return ans;
}

int exgcd(int a,int b,int_ &x,int_ &y){
	if(b == 0){
		x = 1, y = 0; return a;
	}
	int d = exgcd(b,a%b,y,x);
	y -= (a/b)*x; return d;
}

int getInv(int x,int p){
	int_ res, useless;
	if(exgcd(x%p,p,res,useless) != 1)
		return -1; // impossible
	return (res%p+p)%p;
}

int CRT(int n,int m[],int c[]){
	int M = 1, ans = 0;
	for(int i=1; i<=n; ++i)
		M *= m[i];
	for(int i=1; i<=n; ++i){
		int tmp = M/m[i];
		tmp *= getInv(tmp,m[i]);
		ans = (ans+1ll*tmp*c[i])%M;
	}
	return ans;
}

int function(int_ n,int p,int pk){
	if(n <= 1) return 1; int res = 1;
	if(n >= pk){
		for(int i=1; i<=pk; ++i)
			if(i%p != 0)
				res = 1ll*res*i%pk;
		res = qkpow(res,n/pk,pk);
	}
	for(int i=1; i<=n%pk; ++i)
		if(i%p != 0)
			res = 1ll*res*i%pk;
	return 1ll*res*function(n/p,p,pk)%pk;
}

int_ calc(int_ n,int p){
	if(n < p) return 0; return n/p+calc(n/p,p);
}
int exLucas(int_ n,int_ m,int p,int pk){
	int_ pt = calc(n,p)-calc(m,p)-calc(n-m,p);
	int_ jb = function(n,p,pk);
	jb = 1ll*jb*getInv(function(m,p,pk),pk)%pk;
	jb = 1ll*jb*getInv(function(n-m,p,pk),pk)%pk;
	return 1ll*jb*qkpow(p,pt,pk)%pk;
}

int mods[50], yu[50];
int Lucas(int_ n,int_ m,int p){
	int cnt = 0;
	for(int i=2,j; 1ll*i*i<=p; ++i){
		if(p%i != 0) continue;
		for(j=1; p%i==0; j*=i) p /= i;
		mods[++ cnt] = j;
		yu[cnt] = exLucas(n,m,i,j);
	}
	if(p != 1){
		mods[++ cnt] = p;
		yu[cnt] = exLucas(n,m,p,p);
	}
	return CRT(cnt,mods,yu);
}

预处理版适用于模数固定但需要多次计算组合数的情况。复杂度 O ( k p k log ⁡ p ) \mathcal O(kp^k\log p) O(kpklogp) 预处理 + O ( log ⁡ n ) +\mathcal O(\log n) +O(logn) 每次询问。

template < int p, int pk >
struct LUCAS{
	int jc[pk+1]; // factorial
	int invjc[pk+1]; // inv of jc
	LUCAS(){
		int phi = pk/p*(p-1);
		jc[0] = invjc[0] = 1;
		jc[1] = invjc[1] = 1;
		for(int i=2; i<=pk; ++i){
			jc[i] = jc[i-1]; // copy
			invjc[i] = invjc[i-1];
			if(i%p == 0) continue;
			jc[i] = 1ll*jc[i]*i%pk;
			invjc[i] = 1ll*invjc[i]
				*qkpow(i,phi-1,pk)%pk;
		}
	}
	int Lucas(int_ n,int_ m){
		if(n < m || m < 0) return 0;
		int z = 0; int_ ans = 1;
		for(int_ i=n; i; i/=p){
			ans = ans*jc[i%pk]%pk;
			z += i/pk; // how many [1,pk]
		}
		for(int_ i=m; i; i/=p){
			ans = ans*invjc[i%pk]%pk;
			z -= i/pk; // cashier
		}
		for(int_ i=n-m; i; i/=p){
			ans = ans*invjc[i%pk]%pk;
			z -= i/pk; // counter
		}
		if(z > 0) // something good
			ans = ans*qkpow(jc[pk],z,pk)%pk;
		else if(z < 0) // can't be worse
			ans = ans*qkpow(invjc[pk],-z,pk)%pk;
		z = 0; // 现在求 C(n,m) 中 p 的指数
		for(int_ i=p; i<=n; i*=p)
			z += (n/i)-(m/i)-((n-m)/i);
		ans = ans*qkpow(p,z,pk)%pk;
		return ans; // 外接 CRT
	}
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值