拓展盧卡斯定理學習筆記(附拓展中國剩餘定理)

前前言

很久之前學的了,但一直沒有機會用到,就寫個 blogblog 防止忘記吧。

題意

Cnmmod  qC_n^{m}\mod q
其中,n,m1018,q106n,m\leq 10^{18}, q\leq 10^6qq 不一定爲質數。
模板題在這裏!

前言

exLucasexLucas定理 和 LucasLucas 定理一點關係都沒有。。。。
所以根本不需要你會 LucasLucas 定理,不過你得會中國剩餘定理QAQ
好了下面進入正題!!

主要思路

  1. 先將 qq 分解質因數,變爲 q=p1k1p2k2...pmkmq=p_1^{k_1}p_2^{k_2}...p_m^{k_m}
  2. 分別求出 Cnmmod  pikiC_{n}^{m}\mod p_i^{k_i}
  3. 用中國剩餘定理合併

分析

現在的主要問題就是求 n!m!(nm)!mod  piki\dfrac{n!}{m!(n-m)!}\mod p_i^{k_i}
但是 n,mn,m 實在是太太太大了,讓這一切非常難頂。
爲了讓下面存在逆元,我們要把所有的 pp 提出來。
n!=f(n)pan!=f(n)*p^a,其中 gcd(f(n),p)=1\gcd(f(n),p)=1,也就是把所有 pp 的次數都提出來。
於是原式可以寫成這種形式:
n!m!(nm)!mod  pikif(n)f(m)f(nm)pabcmod  piki\dfrac{n!}{m!(n-m)!}\mod p_i^{k_i}\Rightarrow\dfrac{f(n)}{f(m)f(n-m)}*p^{a-b-c} \mod p_i^{k_i}

現在的問題就變成:

  1. 求出 a,b,ca,b,c
  2. 求出 f(n),f(m),f(nm)mod  pikif(n),f(m),f(n-m)\mod p_i^{k_i},然後求波逆元就完事兒了

第一個問題很好求嘛,令 g(n)g(n) 表示 n!n!pp 的次數,有以下遞推式:g(n)=np+g(np)g(n)=\lfloor\dfrac{n}{p}\rfloor+g(\lfloor\dfrac{n}{p}\rfloor)

我們重點看第二個問題!!

第二個問題

我們要快速求出 f(n)mod  pkf(n) \mod p^kn1018,pk106n\leq 10^{18},p^k\leq 10^6
n!=1×2×3..×p...×nn!=1\times 2\times 3..\times p...\times n
我們把 n!n! 分成兩部分,非 pp 的倍數和 pp 的倍數。

pp 的倍數

現在我們把所有 pp 的倍數拿掉,大概是這個樣子:
f(n)=[1×2×3×...×(p1)]×[(p+1)×(p+2)..×(2p1)]×...f(n)=[1\times 2\times 3\times...\times (p-1)]\times[(p+1)\times(p+2)..\times(2p-1)]\times...
我們把每 p1p-1 個數放到一箇中括號裏,稱這個爲一組。其中,第 xx 組爲 [(x1)p+1,xp1][(x-1)p+1,xp-1]
考慮 pk1p^k-1 這個數,它是 pk1p^{k-1} 組的最後一個數。
那麼它的下一組,也就是第 pk1+1p^{k-1}+1 組,就是 [pk+1,pk+p1][p^k+1,p^k+p-1]
而這一組,在模 pkp^k 意義下,和第一組一模一樣!!
也就是 ipk+i (mod pk),(ip1)i\equiv p^k+i~(mod~p^k),(i\leq p-1)
這說明,出現了循環節!!
那我們把每 pk1p^{k-1} 組放在一節,乘積記作 SS
那麼總共有 npk\lfloor\dfrac{n}{p^k}\rfloor 節,這部分乘積爲 SnpkS^{\tiny{\lfloor\dfrac{n}{p^k}\rfloor}}
最後剩的部分的數不超過 pkp^k 個,我們暴力求即可。
於是,我們就在 O(pk)O(p^k) 時間內求出了非 pp 的倍數的乘積,記作 AA

pp 的倍數

考慮 p×2p×3p...×nppp\times 2p\times 3p...\times \lfloor\dfrac{n}{p}\rfloor p
我們去掉所有 pp,就變成 (np)!(\lfloor\dfrac{n}{p}\rfloor)!
接下來我們就是要求 f(np)f(\lfloor\dfrac{n}{p}\rfloor)。這部分遞歸求就可以了。

綜上

f(n)={1(n=1)A×f(n/p)(otherwise)f(n)=\begin{cases}1 &(n=1)\cr A \times f(n/p)&(otherwise)\end{cases}
複雜度是 O(pklogn)O(p^klogn)

最後用中國剩餘定理合併即可。

拓展中國剩餘定理

考慮到可能有人不會 CRTCRT,而且我怕自己忘了,就在這裏把 exCRTexCRT 介紹一下吧。
下面要求以下線性同餘方程組的一個解:
{xa1 (mod b1)xa2 (mod b2)...xam(mod bm)\begin{cases}x\equiv a_1~(mod~b_1)\cr x\equiv a_2~(mod~b_2)\cr{...}\cr x\equiv a_m(mod~b_m)\end{cases}

其中,{bi}\{b_i\} 不一定兩兩互質。

考慮我們已經求出了前 k1k-1 個方程的一個解 XX,現在求前 kk 個方程的一個解。
B=lcm(b1,b2,...,bk1)B=lcm(b_1,b_2,...,b_{k-1})
那麼前 kk 個方程的所有解爲 X+tB(tZ)X+tB(t\in Z)
考慮某個解滿足第 kk 個方程,即:
X+tBak (mod bk)X+tB\equiv a_{k}~(mod~b_k)
我們的目標是求出某個滿足的 tt,移一下項,有:
tBakX (mod bk)tB\equiv a_k-X~(mod~b_k)
g=gcd(B,bk)g=\gcd(B,b_k),如果 gag\nmid a,那麼方程組無解。
否則,方程等價爲:tBa (mod b)tB'\equiv a'~(mod~b')
其中 B=B/g,a=(akX)/g,b=bk/gB'=B/g,a'=(a_k-X)/g,b'=b_k/g
於是有 gcd(B,b)=1\gcd(B',b')=1BB' 關於 bb' 的逆元存在,於是可以求出某個解 tt,即:ta×B1 (mod b)t\equiv a'\times B'^{-1}~(mod~b')
於是我們可以在 O(nlogV)O(nlogV) 時間內求出線性同餘方程組的解。

代碼超級無敵清晰!

總代碼如下

#include <bits/stdc++.h>
#define m_p make_pair
#define N 100005
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;

LL z = 1;
LL read(){
	LL x, f = 1;
	char ch;
	while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1;
	x = ch - '0';
	while(ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + ch - 48;
	return x * f;
}

LL ksm(LL a, LL b, LL p){
	LL s = 1;
	while(b){
		if(b & 1) s = z * s * a % p;
		a = z * a * a % p;
		b >>= 1;
	}
	return s;
}

vector<pair<LL, LL> > d;

void get(LL x){//分解質因數,得到 p 和 p^k
	for(LL i = 2; i <= x / i; i++){
		if(x % i == 0){
			LL tot = 1;
			while(x % i == 0) tot *= i, x /= i;
			d.push_back(m_p(i, tot));
		}
	}
	if(x > 1) d.push_back(m_p(x, x));
}

LL f(LL n, LL p, LL pk){//求 f(n) 
	if(n == 0 || n == 1) return 1;
	LL i, s = 1;
	for(i = 1; i <= pk; i++) if(i % p) s = s * i % pk;
	s = ksm(s, n / pk, pk);
	for(i = n / pk * pk; i <= n; i++) if(i % p) s = s * (i % pk) % pk;
	return s * f(n / p, p, pk) % pk;
}

LL g(LL n, LL p){//求 g(n) 
	if(n < p) return 0;
	return n / p + g(n / p, p);
}

void exgcd(LL a, LL b, LL &x, LL &y){
	if(!b) x = 1, y = 0;
	else{
		exgcd(b, a % b, y, x);
		y -= a / b * x;
	}
}

LL inv(LL a, LL b){
	LL x, y;
	exgcd(a, b, x, y);
	return (x + b) % b;//求逆元 
}

LL excrt(LL n, LL *a, LL *b){//excrt 合併解 
	LL x, B, M, t, k, g;
	__int128 z = 1;
	x = a[1], B = b[1];
	for(LL i = 2; i <= n; i++){
		g = __gcd(B, b[i]);
		M = B / g * b[i];
		if((a[i] - x) % g != 0) return -1;
		exgcd(B / g, b[i] / g, t, k);
		t = (z * t * (a[i] - x) / g % M + M) % M;
		x = (x + z * t * B % M) % M;
		B = M;
	}
	return x;
}

LL a[N], b[N];

LL exLucas(LL n, LL m, LL p){
	LL ans = 1, i, j, k, pk, w, cnt = 0;
	get(p);
	for(auto t: d){
		p = t.first, pk = t.second;
		i = f(n, p, pk);
		j = inv(f(m, p, pk), pk); k = inv(f(n - m, p, pk), pk);
		w = g(n, p) - g(m, p) - g(n - m, p);
		i = i * j % pk * k % pk;
		i = i * ksm(p, w, pk) % pk;
		a[++cnt] = i, b[cnt] = pk;//得到 C(n, m) % p^k 
	}
	return excrt(cnt, a, b);
}

int main(){
	int i, j;
	LL n, m, p;
	n = read(); m = read(); p = read();
	printf("%lld", exLucas(n, m, p));
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章