前前言
很久之前學的了,但一直沒有機會用到,就寫個 防止忘記吧。
題意
求 。
其中,, 不一定爲質數。
模板題在這裏!
前言
定理 和 定理一點關係都沒有。。。。
所以根本不需要你會 定理,不過你得會中國剩餘定理QAQ
好了下面進入正題!!
主要思路
- 先將 分解質因數,變爲
- 分別求出
- 用中國剩餘定理合併
分析
現在的主要問題就是求 。
但是 實在是太太太大了,讓這一切非常難頂。
爲了讓下面存在逆元,我們要把所有的 提出來。
令 ,其中 ,也就是把所有 的次數都提出來。
於是原式可以寫成這種形式:
現在的問題就變成:
- 求出
- 求出 ,然後求波逆元就完事兒了
第一個問題很好求嘛,令 表示 中 的次數,有以下遞推式:
我們重點看第二個問題!!
第二個問題
我們要快速求出 。。
我們把 分成兩部分,非 的倍數和 的倍數。
非 的倍數
現在我們把所有 的倍數拿掉,大概是這個樣子:
我們把每 個數放到一箇中括號裏,稱這個爲一組。其中,第 組爲 。
考慮 這個數,它是 組的最後一個數。
那麼它的下一組,也就是第 組,就是 。
而這一組,在模 意義下,和第一組一模一樣!!
也就是 。
這說明,出現了循環節!!
那我們把每 組放在一節,乘積記作 。
那麼總共有 節,這部分乘積爲 。
最後剩的部分的數不超過 個,我們暴力求即可。
於是,我們就在 時間內求出了非 的倍數的乘積,記作 。
的倍數
考慮 。
我們去掉所有 ,就變成 。
接下來我們就是要求 。這部分遞歸求就可以了。
綜上
複雜度是 。
最後用中國剩餘定理合併即可。
拓展中國剩餘定理
考慮到可能有人不會 ,而且我怕自己忘了,就在這裏把 介紹一下吧。
下面要求以下線性同餘方程組的一個解:
其中, 不一定兩兩互質。
考慮我們已經求出了前 個方程的一個解 ,現在求前 個方程的一個解。
令
那麼前 個方程的所有解爲
考慮某個解滿足第 個方程,即:
我們的目標是求出某個滿足的 ,移一下項,有:
。
令 ,如果 ,那麼方程組無解。
否則,方程等價爲:
其中 。
於是有 , 關於 的逆元存在,於是可以求出某個解 ,即:。
於是我們可以在 時間內求出線性同餘方程組的解。
代碼超級無敵清晰!
總代碼如下
#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;
}