一些定理
裴蜀定理
若关于 (x,y) 的不定方程 (ax+by=c) 有解((a,b,c in mathbf{Z})),则 (c mod gcd(a,b) =0) 。
费马小定理
若 (p) 为质数,且 (gcd(a,p)=1) ,则 (a^{p-1} equiv 1 pmod{p}) 。
欧拉定理
若 (gcd(a,m)=1) ,则 (a^{varphi(m)} equiv 1 pmod{m}) 。
扩展欧拉定理
(a^b equiv egin{cases} a^{b mod varphi(p)}, & gcd(a,p)=1 \ a^b, & gcd(a,p)
eq 1,b<varphi(p) \ a^{b mod varphi(p)+varphi(p)}, & gcd(a,p)
eq 1, b geq varphi(p) end{cases} pmod p)
exgcd
求关于 (x) , (y) 的方程 (ax+by=gcd(a,b)) 的一组解。
设 (ax_1+by_1=gcd(a,b)) , (bx_2+(a mod b)y_2=gcd(b,a mod b)) 。
因为 (gcd(a,b)=gcd(b,a mod b)) ,
所以 (ax_1+by_1=bx_2+(a mod b)y_2) ,
所以 (ax_1+by_1=bx_2+(a-lfloor frac{a}{b}
floor cdot b)y_2) 。
(bx_2+(a-lfloor frac{a}{b}
floor cdot b)y_2=ay_2+b(x_2-lfloor frac{a}{b}
floor cdot y_2)) ,
所以 (ax_1+by_1=ay_2+b(x_2-lfloor frac{a}{b}
floor cdot y_2)) ,
所以 (x_1=y_2) , (y_1=x_2-lfloor frac{a}{b}
floor cdot y_2) 。
注意到当 (b=0) 时令 (x=1) 即可得到解,以其为边界递归即可。
int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1,y=0;
return a;
}
int r=exgcd(b,a%b,x,y),t=x;
x=y,y=t-a/b*y;
return r;
}
CRT
求解如下形式的方程组(其中,(m_1,m_2,cdots,m_k) 两两互质):
(egin{cases} x & equiv & a_1 pmod{m_1} \ x & equiv & a_2 pmod{m_2} \ & vdots \ x & equiv & a_k pmod{m_k} \ end{cases})
令 (M=prod limits_{i=1}^k m_i) , (w_i=frac{M}{m_i}) , (w_i^{-1}) 为 (w_i) 在模 (m_i) 意义下的逆元。
则 (x_{min}=sum limits_{i=1}^k a_iw_iw_i^{-1} mod M) 。
for(int i=1;i<=k;i++)
{
int w=M/m[i];
int x=0,y=0;
exgcd(w,m[i],x,y);
if(x<0) x+=m[i];
ans+=a[i]*w*x%M,ans%=M;
}
exCRT
考虑上面的方程组中模数不互质的情况。
设当前求解到第 (i) 个方程,前 (i-1) 个方程的最小解为 (x) ,(operatorname{lcm}(m_1,m_2,cdots,m_k)=M) ,
则前 (i-1) 个方程的通解为 (x+tM) 。
那么,现在要找到一个 (t) ,使 (x+tM equiv a_i pmod{m_i}) 。
这个式子显然是可以使用 exgcd 求解的。
用裴蜀定理判一下无解。
for(int i=1;i<=n;i++)
{
int res=(a[i]-X%m[i]+m[i])%m[i];
int x=0,y=0;
int d=exgcd(M,m[i],x,y);
if(res%d) puts("-1"),exit(0);
X+=x*res/d*M;//算上倍数,更新答案
M*=m[i]/d,X+=M,X%=M;
}
Lucas
对于质数 (p) ,有 (displaystyle inom{n}{m} equiv displaystyle inom{lfloor frac{n}{p}
floor}{lfloor frac{m}{p}
floor} cdot displaystyle inom{n mod p}{m mod p} pmod p) 。
在模数不大且为质数时,可以使用它来递归算组合数。
int lucas(int n,int m,int p)
{
if(!m) return 1;
return lucas(n/p,m/p,p)*C(n%p,m%p,p)%p;
}
代码中, C 函数为暴力算组合数。
exLucas
算模数非质数时的组合数。
先将模数 (p) 质因数分解,设 (p=prod limits_{i=1}^k q_i^{a_i}) (其中 (q_i) 为质数)。
可以构造出下面的同余方程组:
(egin{cases} displaystyle inom{n}{m} & equiv & b_1 pmod{q_1^{a_1}} \ displaystyle inom{n}{m} & equiv & b_2 pmod{q_2^{a_2}} \ & vdots \ displaystyle inom{n}{m} & equiv & b_k pmod{q_k^{a_k}} \ end{cases})
因为这个同余方程组的所有模数都互质,所以只要求出 (b_{1 cdots k}) 即可用 CRT 求出 (displaystyle inom{n}{m}) 。
现在,问题转化为求 (displaystyle inom{n}{m} mod p^k) 的值((p) 为质数) ,即 (frac{n!}{m!(n-m)!} mod p^k) 的值。
但是,分母在模 (p^k) 意义下不一定有乘法逆元,所以要再进行一些转化。
设 (n!) 中包含 (x) 个 (p) 因子, (m!) 中包含 (y) 个 (p) 因子, ((n-m)!) 中包含 (z) 个 (p) 因子,
则原式可以转化为 (p^{x-y-z}dfrac{dfrac{n!}{p^x}}{dfrac{m!}{p^y}dfrac{(n-m)!}{p^z}} mod p^k) 。
现在,问题又转化为求 (frac{n!}{p^x} mod p^k) 的值 ((p) 为质数)。
对 (n!) 进行转化:
首先,把 (n!) 中所有为 (p) 的倍数的乘数和非 (p) 的倍数的乘数分开,(n!=(p imes 2p imes 3p imes cdots)(1 imes 2 imes 3 imes cdots)) 。
因为 (1) ~ (n) 中有 (lfloor frac{n}{p}
floor) 个 (p) 的倍数,所以进一步拆开,得 (p^{lfloor frac{n}{p}
floor}(lfloor frac{n}{p}
floor)!prod limits_{i=1,i mod p
eq 0}^n i) 。
因为模数为 (p^k) ,所以再把后半个式子每 (p^k) 个分成一组,得 (p^{lfloor frac{n}{p}
floor}(lfloor frac{n}{p}
floor)!(prod limits_{i=1,i mod p
eq 0}^{p^k} i)^{lfloor frac{n}{p^k}
floor}(prod limits_{i=p^k imes lfloor frac{n}{p^k}
floor, i mod p
eq 0}^{n} i)) 。
因为还要除以 (p^x) ,所以 (p^{lfloor frac{n}{p}
floor}) 一定没有了,但是, ((lfloor frac{n}{p}
floor)!) 中为 (p) 的倍数的乘数也要除去。
设 (f(x)=frac{n!}{p^x}) ,其中 (x) 同样代表 (n!) 中包含 (p) 因子的数目,
则 (f(n)=f(lfloor frac{n}{p}
floor)(prod limits_{i=1,i mod p
eq 0}^{p^k} i)^{lfloor frac{n}{p^k}
floor}(prod limits_{i=p^k imes lfloor frac{n}{p^k}
floor, i mod p
eq 0}^{n} i)) ,递归计算即可。
代回原式中,发现还有 (p^{x-y-z}) 无法计算,考虑如何计算 (x,y,z)。
下面以 (frac{n!}{p^x}) 中的 (x) 为例。
设 (g(x)=) 上面的 (x) 。
观察前面化出的阶乘的式子,即可得到 (g(n)=g(lfloor frac{n}{p}
floor)+lfloor frac{n}{p}
floor) ,同样递归计算即可。
int f(int x,int p,int P)//P=p^k
{
if(!x) return 1;
int sum=1;
for(int i=1;i<=P;i++)
if(i%p) sum*=i,sum%=P;
sum=POW(sum,x/P,P);
for(int i=x/P*P;i<=x;i++)
if(i%p) sum*=i%P,sum%=P;
return f(x/p,p,P)*sum%P;
}
int g(int x,int p)
{
if(!x) return 0;
return g(x/p,p)+x/p;
}
int C(int n,int m,int p,int P)
{
return f(n,p,P)*inv(f(m,p,P),P)%P*inv(f(n-m,p,P),P)%P*POW(p,g(n,p)-g(m,p)-g(n-m,p),P)%P;
}
代码中, POW 为快速幂函数,inv 为求逆元函数。
BSGS
求解方程 (a^x equiv b pmod p) ,其中 (a,p) 互质。
令 (x=At-B) ((t) 为定值),
则 (a^{At-B} equiv b pmod p) ,
因为 (a,p) 互质,所以 (a^{At} equiv ba^B pmod p) 。
可以枚举 (B) 的取值,存进 Hash 表中。
再枚举 (A) 的取值,判断 Hash 表中是否存在这个值,若存在,则找到了一个 (x) 的取值。
取 (t=lceil sqrt p
ceil) 时复杂度最优。
代码用 map 实现。
map <int,int> mp;
int t=ceil(sqrt(P));
int sum=b,s=1;
mp[sum]=0;
for(int i=1;i<=t;i++) sum*=a,sum%=P,s*=a,s%=P,mp[sum]=i;
sum=s;
for(int i=1;i<=t;i++)
{
if(mp.count(sum)) printf("%lld",i*t-mp[sum]),exit(0);
sum*=s,sum%=P;
}
puts("-1");
exBSGS
同样求解上面的方程,但是 (a,p) 不保证互质。
在 (a,p) 不互质时,不存在 (a) 在模 (p) 意义下的逆元,也就没有上面的推导。
考虑如何让 (a,p) 互质。
设 (gcd(a,p)=d_1) ,方程两边同除以 (d_1) ,得到 (frac{a}{d_1} cdot a^{x-1} equiv frac{b}{d_1} pmod{frac{p}{d_1}}) ,
若当前还没有满足 (a) 与模数互质,则继续设 (gcd(a,frac{p}{d_1})=d_2) ,方程两边同除以 (d_2) ,得到 (frac{a}{d_1d_2} cdot a^{x-1} equiv frac{b}{d_1d_2} pmod{frac{p}{d_1d_2}}) ,
重复这样的操作,直到 (a) 与模数互质。
设进行了 (k) 次操作,每次操作时得到的 (d) 的乘积为 (D) ,
则当前方程转化为了 (frac{a^k}{D} cdot a^{x-k} equiv frac{b}{D} pmod{frac{p}{D}}) 。
此时,就可以用普通的 BSGS 求解了。
注意,每次操作过后要用裴蜀定理判一下无解,还要特判 (x leq k) 的情况。
int BSGS(int x)
{
mp.clear();
int t=ceil(sqrt(P));
int sum=b,s=1;
mp[sum]=0;
for(int i=1;i<=t;i++) sum*=a,sum%=P,s*=a,s%=P,mp[sum]=i;
sum=x*s%P;
for(int i=1;i<=t;i++)
{
if(mp.count(sum)) return i*t-mp[sum];
sum*=s,sum%=P;
}
return -1;
}
int exBSGS()
{
if(b==1||p==1) return 0;
int k=0,sum=1;
while(1)
{
int d=gcd(a,P);
if(d==1) break;
if(b%d) return -1;
k++,b/=d,P/=d,sum*=a/d,sum%=P;
if(sum==b) return k;
}
int ans=BSGS(sum);
return (ans!=-1)*k+ans;
}
原根
阶:设 (a,p) 为两个正整数,且 (gcd(a,p)=1) ,则使 (a^x equiv 1 pmod p) 成立的最小正整数 (x) ,即为 (a) 模 (p) 的阶,记作 ( ext{ord}_p a) 。
设 (g,a) 为两个正整数,且 (gcd(g,a)=1) ,若 ( ext{ord}_a g=varphi (a)) ,则 (g) 为 (a) 的一个原根。
若一个数有原根,则它一定可以表示为 (2,4,p^k,2p^k) 的形式((p) 为奇质数,(k) 为任意正整数)。
若 (g) 为 (a) 的原根,则对于任意 (varphi(a)) 的质因子 (p) ,都有 (g^{frac{varphi(a)}{p}}
ot equiv 1 pmod a) 。
设 (g) 为 (a) 的最小原根,则集合 (S={g^k | 1 leq k leq varphi(a),gcd(k,varphi(a))=1}) 中的元素均为 (a) 的原根。
因此,一个数 (a) 有 (varphi(varphi(a))) 个原根,且这些原根模 (a) 两两不同余。
同时,若一个数 (a) 存在原根,则它的最小原根是不大于 (a^{frac{1}{4}}) 级别的。所以求最小原根时可以直接枚举。
int find(int x)
{
int tot=0;
int X=phi[x];
for(int i=2;i*i<=X;i++)
if(X%i==0)
{
a[++tot]=i;
while(X%i==0) X/=i;
}
if(X>1) a[++tot]=X;
for(int i=1;;i++)
{
bool r=true;
if(POW(i,phi[x],x)!=1) r=false;//原根的定义
for(int j=1;j<=tot;j++)
if(POW(i,phi[x]/a[j],x)==1)
{
r=false;
break;
}
if(r) return i;
}
}
void get(int x)
{
int t=find(x),s=1;
for(int i=1;i<=phi[x];i++)
{
s*=t,s%=x;
if(gcd(i,phi[x])==1) ans[++sum]=s;
}
}