模板汇总3.1_数学1

1.线性筛

①什么是线性筛

一种可以在$O(n)$时间内筛出$1->n$所有素数/求出$1->n$积性函数函数值的筛法,所谓筛法就是指“检定某种数的算法”。其实线性筛能求出积性函数函数值就是因为它能筛素数。

②线性筛的原理

丢掉低效率的枚举和那些乱七八糟地把素数判定法当筛法的东西,我们直接从一个效率很接近线性筛的的筛法— —埃氏筛(Eratosthenes 筛法)说起。(等等不是要说线性筛吗quq)

埃氏筛的原理是这样的:我从$1$扫过去,碰到一个素数就把$1->n$以内所有这个素数的倍数全筛掉,很好理解也很好写。缺点就是一个合数很可能会被多个素数筛,比如说$6$会先被$2$筛一遍又被$3$筛一遍,因而这个算法的时间复杂度是$O(nlog$ $log$ $n)$的(用调和级数很好证,不证了233)

怎么改进到$O(n)$呢,我们要让每个每个合数只被其最小素因子筛一次

怎么做的呢?我们还是用素数乘上几倍去筛合数,但是我们再限制一下这个乘的倍数,每次用最外层枚举的那个$i$做倍数,就能保证每个合数只被最小素因子筛一次了

我的另一篇文章里有许多关于线性筛的拓展内容quq

③线性筛的时间复杂度

时间复杂度:$O(n)$

④线性筛的具体实现

 1 #include<cstdio>
 2 const int N=10000005;
 3 int pri[N],npr[N],n;
 4 void euler_sieve(int sc)
 5 {
 6     npr[1]=true;
 7     for(int i=2,sz=0;i<=sc;i++)
 8     {
 9         if(!npr[i]) pri[++sz]=i;
10         for(int j=1;j<=sz&&i*pri[j]<=sc;j++)
11         {
12             npr[i*pri[j]]=true;
13             if(!i%pri[j]) break;
14         }
15     }
16 }
17 int main ()
18 {
19     scanf("%d",&n);
20     euler_sieve(n);
21      return 0;    
22 }
View Code

2.乘法逆元

①什么是乘法逆元

我们在做计数问题时,常常会遇到取模与除法,问题是模运算下不能做除法,比如:

$(24/6)$%$3=1$

$(24$%$3)/(6$%$3)=gg$

那运算过程中如何取模呢?于是有了乘法逆元这个东西。

一个数的乘法逆元是存在于一个剩余系下的,若$a*ia\equiv 1$ $mod$ $p$则称$ia$为$a$在模$p$剩余系下的逆元(当然反过来也一样)。注意:一个数只有在与其互质的数的剩余系下有乘法逆元。

②如何求一个数的乘法逆元

假设我们要求数$a$在模$p$剩余系下的逆元。有三种方法:扩展欧几里得(普适),费马小定理(简易)与递推(高效)

扩展欧几里得算法求逆元:

因为$a*b\equiv x$ $mod$ $p$即$a*b+k*p= x$ 

先做一遍$exgcd(a,p,x,y)$,$x$即为$a$在模$p$剩余系下的逆元($y$即为$p$在模$a$剩余系下的逆元)

利用欧拉定理求逆元:

由$a^{φ(p)}\equiv1$ $mod$ $p$可得

$a*a^{φ(p)-1}\equiv1$ $mod$ $p$ 即 $a^{φ(p)-1}\equiv\frac{1}{a}$ $mod$ $p$

所以有$inv[a]=a^{φ(p)-1}(mod$ $p)$

递推求逆元:

因为$\lfloor P/x\rfloor x+P\%x\equiv0(\text{mod }P)$,故有$x^{-1}\equiv-(P\%x)^{-1}\lfloor P/x\rfloor(\text{mod }P)$

$i:2->n$有$inv[i]=((p-\frac{p}{i})*inv[p$ $mod$ $i]+p)$ $mod$ $p$;

③求一个数乘法逆元的时间复杂度

扩展欧几里得算法&费马小定理$->O(log$ $p)$,递推:$O(1)$

④求乘法逆元的具体实现

还有放的必要么,偷个懒吧quq

⑤关于乘法逆元的扩展

$O(n)$求$1->n$阶乘的逆元

事实上这个可能常用一点,做组合数的题经常用到。

先用exgcd或者费马小定理求出来$n!$的逆元,然后逆过来推

3.高斯消元

①高斯消元是什么

高斯消元用来在$O(n^3)$的复杂度内求出一组线性方程组的解,你可以理解为解一个多元一次方程组。

②高斯消元的原理

我们是怎么手动消元的呢?小学/初中老师可能讲过两种方法:“带入法”和“相减法”(反正就这个意思吧233),高斯消元就是让计算机去做“相减法”来解方程组

具体主要是两个过程:消元和回带

1.消元:枚举各行。调整整行去与下面的行作差,消去下面所有本行第一列系数非零的未知数。不断重复这个过程,如果方程组有唯一解则原方程组会被消成一个倒三角形;如果方程组有无穷多解会有某行出现“少”未知数的情况,这是因为“系数成比例”,在消前面的未知数时把后面一并消掉了;如果方程组无解,则会出现常数1=常数2的情况。

2.回带:从最后一行,不断将已知未知数带入上面的方程,最后每个未知数出现在左上到右下的斜对角线上(不算最后一列的话),解就出现在每行的最后一列。

你可能要问问什么上面那张图几个方程的顺序边消边变,这是为了减小误差。因为高斯消元大部分情况得到的解都不是整数,我们为了保证精度要用每个未知数对应系数最大的方程去消其他方程,这样误差会最小(好比$\frac{1}{3}$误差就会比$\frac{1}{30}$大)。

另一个保证精度的方法就是像上文所说的,把方程组消成倒三角形,然后单独向上回带~网上很多写法都是边调整系数边消,最后直接把解在对角线上消出来,然而这样误差会大一些......

③高斯消元的时间复杂度

时间复杂度:$O(n^3)$

④高斯消元的具体实现

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int N=110;
 7 const double eps=1e-8;
 8 double equ[N][N]; int n; 
 9 void Guass()
10 {
11     for(int i=1;i<=n;i++)
12     {
13         int tmp=i;
14         for(int j=i+1;j<=n;j++)    
15             if(fabs(equ[j][i])>fabs(equ[tmp][i])) tmp=j;
16         for(int j=i;j<=n+1;j++)
17             swap(equ[i][j],equ[tmp][j]);
18         if(fabs(equ[i][i])<=eps) 
19             printf("No Solution"),exit(0);
20         for(int j=1;j<=n;j++)
21             if(i!=j)
22             {
23                 double tep=equ[j][i]/equ[i][i];
24                 for(int k=i;k<=n+1;k++)
25                     equ[j][k]-=tep*equ[i][k];
26             }
27     } 
28     for(int i=1;i<=n;i++)
29     {
30         if(fabs(equ[i][i])<=eps) 
31             printf("No Solution"),exit(0);
32         equ[i][n+1]/=equ[i][i];
33     }
34 }
35 int main()
36 {
37     scanf("%d",&n);
38     for(int i=1;i<=n;i++)
39         for(int j=1;j<=n+1;j++)
40             scanf("%lf",&equ[i][j]);
41     Guass(); 
42     for(int i=1;i<=n;i++)
43         printf("%.2f\n",equ[i][n+1]);
44     return 0;
45 } 
View Code

⑤高斯消元的其他拓展

模合数剩余系下的高斯消元

高斯消元是可以处理同余方程组的,只需要把消元时的除法换成乘逆元即可。但是模合数剩余系下怎么做呢?辗转相减法。这是一道例题中的代码 

 1 for(int i=1;i<=n;i++)
 2     {
 3         for(int j=i+1;j<=n;j++)
 4         {
 5             long long tmp=equ[i][i],tep=equ[j][i];
 6             while(tep)
 7             {
 8                 long long th=tmp/tep;
 9                 tmp%=tep,swap(tmp,tep);
10                 for(int k=i;k<=n;k++) 
11                     equ[i][k]=(equ[i][k]-equ[j][k]*th%mod+mod)%mod;
12                 for(int k=i;k<=n;k++)
13                     swap(equ[i][k],equ[j][k]);
14                 flag=-flag;
15             }
16         }
17         if(!equ[i][i]) {printf("0");return 0;}
18         ans=ans*equ[i][i]%mod;
19     }

4.(扩展)中国剩余定理((EX)CRT)

①什么是(扩展)中国剩余定理

中国剩余定理用于求解模线性方程组......等等什么是模线性方程组?

在我国南北朝时期的数学著作《孙子算经》卷下第二十六题中,有这样一个问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?用数学语言表述一下这个问题,我们设物品数为$x$,题目即是让我们求解满足

$x\%3=2----$①

$x\%5=3----$②

$x\%7=2----$③

的$x$,这种方程组就叫做模线性方程组。你可能看到了$3,5,7$三个数是互质的,事实上中国剩余定理$(CRT)$只能求解这种情况,如果模数不两两互质就会gg。为了避免这些麻烦,我们直接学习扩展中国剩余定理$(EXCRT)$,它可以处理模数不互质的情况。

②扩展中国剩余定理的原理

这是一个推式子的过程

显然我们只需要知道如何两两合并方程然后一路合过去就好了,以合并

$x \equiv a_1(mod$ $p_1)$

$x \equiv a_2(mod$ $p_2)$

两个方程为例,我们的目标是得出新的模数和余数,显然新的模数$p'$即$lcm(p_1,p_2)$,那么如何得到新的余数?

首先将两个方程改写为不定方程的形式

$x=p_1*k_1+a_1$

$x=p_2*k_2+a_2$

显然有

$p_1*k_1+a_1=p_2*k_2+a_2$

$p_1*k_1-p_2*k_2=a_2-a_1$

那么套用EXGCD即可得到$k_1$的一个解$b$,这样我们回带就能得到新的余数了!设$g=gcd(p_1,p_2)$而$k_1$的一个通解可以表示为

$b*(a_2-a_1)/g+m*p_2/g(m∈Z)$

那么根据题意用$p_2/g$调整出$k_1$的最小正整数解$k'$,之后回带得到新余数$a'$,那么有

$a'=a_1+p_1*k'$

这样就完成了合并辣,最后注意是最小正整数解=。=

$a'=(a'\%p'+p')\%p'$

③扩展中国剩余定理的时间复杂度

时间复杂度:$O(nlog$ $p)(p$是模数$)$

④扩展中国剩余定理的具体实现

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=100005;
 6 long long mod[N],res[N];
 7 long long n,md,rs,xx,yy;
 8 long long exgcd(long long a,long long b,long long &x,long long &y)
 9 {
10     if(!b){x=1,y=0; return a;}
11     long long tmp=exgcd(b,a%b,x,y),tep=x;
12     x=y,y=tep-a/b*y; return tmp;
13 }
14 long long EXCRT()
15 {
16     long long md=mod[1],rs=res[1];
17     for(int i=1;i<=n;i++)
18     {
19         long long p=exgcd(md,mod[i],xx,yy),tmp=mod[i]/p;
20         if((res[i]-rs)%p) exit(0); //No solution
21         xx*=(res[i]-rs)/p,xx=(xx%tmp+tmp)%tmp;
22         rs+=md*xx,md=md*tmp;
23     }
24     return (rs%md+md)%md;
25 }
26 int main ()
27 {
28     scanf("%lld",&n);
29     for(int i=1;i<=n;i++)
30         scanf("%lld%lld",&mod[i],&res[i]);
31     printf("%lld",EXCRT());
32     return 0;
33 }
View Code
posted @ 2018-09-18 17:26  Speranza_Leaf  阅读(145)  评论(0)    收藏  举报