好像有一段时间没有写算法blog了。。
正好最近停课备战Noip,最近主要复习数论,那么就借此机会瞎扯一些简单的数论吧。。。原谅本人数学渣。
Part 1 欧拉函数、筛法
众所周知,筛法主流形态为2种,1种是质数筛,比较简单就不介绍了,另外一种是线性的欧拉筛,这里就和欧拉函数一起讲一下吧。。
欧拉函数
欧拉函数(( varphi (x)))的定义是这么一个东西:在([1,x])范围内与正整数(x)互质((gcd(a,x)=1),后文简记为((a,x)=1))的正整数(a)的个数。
欧拉函数有很多性质,由于本人数学渣,这些性质就不给出证明了,想看证明的自行移步dalao博客qaq
一些简单的性质:
I.欧拉函数为积性函数,但不是完全积性函数:
通俗的讲就是(varphi (xy) = varphi(x) varphi(y)) 当且仅当((x,y)=1)
II.对于一个正整数N的素数幂分解(N=p1^{q1}*p2^{q2}*...*pn^{qn})
于是有(varphi (N)=N*(p1-1)/p1*(p2-1)/p2*...*(pn-1)/pn)。
上述公式可以用欧拉函数的积性证明,这也是求一个大整数N欧拉函数值的最优策略,复杂度为(O(log n)) 需要预处理出(sqrt n)中的所有质数,否则可以用(O(sqrt n))的效率枚举素因子解决。
贴一个求大整数欧拉函数的板于此:
inline ll getphi(ll x){
R ll res=x;
for (R int i=1; i<=pn; ++i)
if (!(x%pr[i])){
res=res/pr[i]*phi[pr[i]];
while((!(x%pr[i]))) x/=pr[i];
}
if (x>1) res=res/x*(x-1);return res;
}
III.除了(N leq 2)的情况,(varphi (N))均为偶数。
IV.对于一个正整数N,(N=Sigma varphi(d) (d|N))
线性筛法
了解完欧拉函数之后,我们来讲一下线筛以及如何用线筛求欧拉函数。
先贴个线筛求欧拉函数模板
void pre(){
for (R int i=2; i<=MM; ++i){
if (!b[i]){
pr[++pn]=i;
phi[i]=i-1;
}
for (R int j=1; j<=pn; ++j){
if (1ll*i*pr[j]>MM) break;
b[i*pr[j]]=1;
if (i%pr[j]==0){
phi[i*pr[j]]=phi[i]*pr[j];
break;
}phi[i*pr[j]]=phi[i]*(pr[j]-1);
}
}
}
把含有phi数组的话去掉,就是正常的线筛
先解释一下线筛复杂度为O(n)的原因:
每个数只会被其最小素因子所筛,故每个数只会被筛一次,复杂度为O(n)。
先注意一句话:if (i%pr[ j ]==0) break;
证明如下:
当(pr[j]|i) 时,pr数组中的素数是递增的,因此对于某个合数(N=i*pr[j+1]),肯定在这之前被某个数(k)筛掉。
由于(pr[j]|i),因此设(i=k*pr[j]),那么(N=kpr[j]*pr[j+1]=k'pr[j]),故其被筛,素数表中位置在(pr[j])之后的同理,于是得证。
同时由此,也得出了一定是被最小素因子筛掉的结论。
接下来讲一下线筛求欧拉函数做法的原因:
1.每个合数只被筛了一次;
2.对于素数(p),(varphi(p)=p-1);
3.欧拉函数的积性使得:(varphi(ab)=varphi(a)varphi(b)((a,b)=1))
4.对于素数(p),(varphi (p^k)=(p-1)*p^{k-1})
因此,每个数的欧拉函数值只会被算一次,当(pr[j]|i)时,(varphi(i*pr[j]))的计算公式可以从4推出,否则,可以从2,3推出,于是可以这样来线性求欧拉函数。
这大概就是第一部分的内容了吧,还是那句话,原谅本人数学渣,如果有不理解的地方还请见谅。
Part 2 拓欧(exgcd)、乘法逆元
简单介绍一下exgcd以及与其相关的乘法逆元。
拓展欧几里得算法(exgcd)
首先基础知识欧几里得算法求gcd以及其证明就略过了。
exgcd主要做的是这么一件事:对于给定的裴蜀等式求一组整数解。
先介绍一下裴蜀等式:(ax+by=kgcd(a,b)) 这是一定存在整数解的,而exgcd的主要任务就是求一组整数解。
exgcd的范畴:解二元线性方程(ax+by=gcd(a,b))的一组整数解。顺便一提裴蜀定理有个结论是对于二元线性方程存在整数解的充要条件同样是常数项被gcd(a,b)整除。
先贴个板:
inline void exgcd(int a,int b,int &x,int &y){
if (!b){x=1,y=0; return;}exgcd(b,a%b,x,y);
register int t=x;x=y;y=t-a/b*y;
}
至于原因,我也不是搞得很懂,大概就是在递归中得到答案,最后找到边界。
乘法逆元
接下来介绍一下乘法逆元。
在竞赛中,常常碰到这么一件事:求(a/b (mod M))
首先明确一点,除法取模是没有分配率的,然后讲逆元。
逆元定义如下:如果(ax=1(mod p))且((a,p)=1),那么称x为a在模p意义下的乘法逆元,记(x)为(a^{-1}(mod p))。
因此,可以将(a/b(mod M)) 转化为(a*b^{-1} (mod p))这就是乘法逆元的作用。
接下来简单介绍一下求乘法逆元的几种方法
I. exgcd
主要是通过下述递推得到的方法:
(ax=1(modp)) = > (ax-bp=1) =>又((a,p)=1),故可以利用exgcd求解。
II.欧拉定理(快速幂)
欧拉定理就是(a^{varphi (p)} = 1 (mod p)) =>$ a^{varphi (p) -1}*a = 1 (mod p) $
=>(a^{-1}(mod p)=a^{varphi (p) -1})
上式在(p)为质数的时候就是费马小定理,也只建议在质数的情况下使用。
III.线性递推法
记(i)的逆元为(inv[i]),那么有如下递推式:(inv[i]=(M-M/i)*inv[M \% i] \% M) ,(inv[1]=1)
边界条件不解释,接下来证明递推式:
记(p=M/i ,q=M \% i)
则(pi+q=0(mod M))
于是有(-pi=q (mod M))
因为((i,M)=1,(q,M)=1)两边同除以iq有(-pinv[q]=inv[i] (mod M))
代入得((-M/i) inv[M\%i] =inv[i] (modM))
于是有递推式。给出模板:
inv[1]=1;for (R int i=2; i<=n; ++i)
inv[i]=(1ll*p-p/i)*inv[p%i]%p;
于是就讲完了。。。