关于数论的整理

一丶扩展欧几里得算法

 

欧几里得算法是 辗转相除法?一种求两个数的最大公约数的算法:gcd(a,b)=gcd(b,a%b)

在这里不详细展开讲,直接给出代码

1 int gcd(int a,int b){
2     return b==0 ? a : gcd(b,a%b);
3 }
欧几里得

那么来说扩展欧几里得:

找出一对数对(x,y),是的ax+by=gcd(a,b)

∵gcd(a,b)=gcd(b,a%b) 欧几里得定理

∴ax1 + by1 = bx2+ (a%b)y2

∵在整除意义下,a%b=a-(a/b)*b

∴ax1 + by1 = bx2 + [a-(a/b)*b] y2

∴ax1 + by1 = bx2 + a* y2 - b*(a/b)*y2  右边展开

∴ax1 + by1 = ay2 + b*[x2 - (a/b) *y2] 右边合并同类项

根据恒等定理得,x1 = y2 ,y1 = x2 - (a/b)y2

所以问题就变得简单了,使用递归求解

边界条件:gcd(a,0)= 1 * a - 0 * 0 = a

void gcd(int a,int b,int &d,int &x,int &y)
{
    if(b==0){d=a;a=1;y=0;}
    else 
    {
        gcd(b,a%b,d,y,x);
        y-=x*(a/b);
    }
}

(若x为方程解,则x≡y(modn)的其他整数y也是方程解)

二、同余方程

 

定理1  若gcd(a,b)=d,则一定能找出一组(x,y),满足ax+by=d

证明:叫裴蜀定理?

设存在x,y使ax+by=d,d是ax+by取值中的最小正整数,d≠1.再设am+bn=e,则e≥d .若d不整除e,对e做带余除法.必定存在p,r使e=pd+r

所以d一定能写成ax+by的形式。

a = b*q1 + r1

b=  r1*q2+ r2

r1= r2*q3+r3

……

r1=q-b*q1

r2=b-r1*q2

r3=r1-r2*q3

……

定理2  若gcd(a,b)=1,则方程ax≡c(mod b) 在[0,b-1]上有唯一解

定理3  若gcd(a,b)=d,则方程ax≡c(mod b) 在[0,b/d-1]上有唯一解

来看这么一道题:同余方程

noip 2012 提高组 同余方程 http://cogs.pro/cogs/problem/problem.php?pid=1265

axb(modn)

可以看出 a-b是n的整数倍

那么设这个倍数为y

则ax-b=n*y

移项得ax-ny=b(不定方程 a,n,b为已知量,x,y为未知量)

给出代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstdio>
 4 using namespace std;
 5 long long modd(long long m,long long n,long long &x,long long &y) 
 6 {
 7     if(!n) 
 8     {
 9         x=1;
10         y=0;
11         return m;
12     } 
13     else 
14     {
15         long long r=modd(n,m%n,x,y);
16         long long t=x;
17         x=y;
18         y=t-m/n*y;
19         return r;
20     }
21 }
22 
23 int main() {
24     long long n,m,x,y;
25     cin>>m>>n;
26     long long  gcd=modd(m,n,x,y);
27     while(x<0)x+=n/gcd;
28     cout<<x;
29     return 0;
30     fclose(stdin);
31     fclose(stdout);
32 }
同余方程

 

三丶筛法求素数

用筛法构造素数表

思想:对不超过n的非负整数a,删除2a,3a,4a..........,当处理完所数后没有被筛去的就是素数。

用数组 vis[i]表示,若vis[i]=1,表示已被筛去,i不是素数,=0表i是素数。

memset(vis,0,sizeof(vis));
vis[1]=1;
for(int i=2;i<=n;i++)
  for(int j=i*2;j<=n;j+=i) {vis[j]=1;}

时间复杂度为O(nlogn)

改进:可以把a限定为素数;在两个for中间加一个if(!vis[i])

外层循环循环到sqrt(n)+0.5就好,因为n的倍数都能用 根n 得到 +0.5防止向下取整数

内层循环中的j=i*2可以改成i*i,因为i*2已经在i=2时筛掉了

下边是代码

memset(vis,0,sizeof(vis));
vis[1]=1;
for(int i=2;i<=sqrt(n)+0.5;i++)
  if(!vis[i])
  for(int j=i*i;j<=n;j+=i)
  {
      vis[j]=1;
  }
 1 #include <iostream>
 2 using namespace std;
 3 
 4 const int MD = 1e9 + 7;
 5 
 6 int pow(int n, int m) 
 7 {
 8     if(m == 0) return 1;
 9     
10     int t = pow(n, m / 2);
11     
12     t = 1LL * t * t % MD;
13     if(m&1) t = 1LL * t * n % MD;
14     
15     return t;
16 }
17 
18 int pow2(int n, int m)
19 {
20     int r(1), s(n);
21     for(; m; m>>=1, s = 1LL*s*s%MD) 
22         if(m&1) r = 1LL*r*s%MD;
23     return r;
24 }
25 
26 int main()
27 {
28     int n, m;
29     cin >> n >> m;
30     cout << pow(n, m) << endl;
31     cout << pow2(n, m) << endl;
32     return 0;
33 }
pow

 

模板:

 

三丶 斐波那契数列

通项公式:

 1 #include<iostream>
 2 #include<cmath>
 3 #include<cstdio>
 4 using namespace std;
 5 int main()
 6 {
 7     int n;
 8     scanf("%d",&n);
 9     double x=sqrt(5.0);
10     int ans=( pow ( ( ( 1+x ) / 2.0 ) , n ) / x - pow ( ( ( 1 - x ) / 2.0 ), n ) /x);
11     printf("%d",ans);
12     return 0;
13 }
通项公式

 

四丶求二元一次不定方程

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 using namespace std;
 5 int x,y;
 6 int gcd(int a,int b)
 7 {
 8     if(b==0)
 9     return a;
10     else 
11     return gcd(b,a%b);
12 }
13 int exgcd(int a,int b,int &x,int &y)
14 {
15     if(b==0)
16     {
17         x=1;
18         y=0;
19         return a;
20     }
21     int r=exgcd(b,a%b,x,y);
22     int temp=x;
23     x=y;
24     y=temp-(a/b)*y;
25     return r;
26 }
27 int main()
28 {
29     int a,b,c;
30     scanf("%d%d%d",&a,&b,&c);
31     int y=gcd(a,b);
32     if(c%y!=0)
33     {
34         printf("-1");
35         return 0;
36     }
37     else exgcd(a,b,x,y);
38     while(x<0)
39     {
40         x=x+b;
41         y=y+b;
42     }
43     x*=c;
44     printf("%d %d",x,y);
45     return 0;
46 }
二元一次不定方程

 

五丶快速幂

下面是从别人博客找的,自己就懒得写了http:快速幂

快速幂这个东西比较好理解,但实现起来到不老好办,记了几次老是忘,今天把它系统的总结一下防止忘记。

  首先,快速幂的目的就是做到快速求幂,假设我们要求a^b,按照朴素算法就是把a连乘b次,这样一来时间复杂度是O(b)也即是O(n)级别,快速幂能做到O(logn),快了好多好多。它的原理如下:

  假设我们要求a^b,那么其实b是可以拆成二进制的,该二进制数第i位的权为2^(i-1),例如当b==11时

a^11=a^(2^0+2^1+2  11的二进制是1011,11 = 2³×1 + 2²×0 + 2¹×1 + 2º×1,因此,我们将a¹¹转化为算 a^(2^0)*a^(2^1)*a^(2^3) 
,看出来快的多了吧原来算11次,现在算三次,但是这三项貌似不好求的样子....不急,下面会有详细解释。
  由于是二进制,很自然地想到用位运算这个强大的工具: &  和 >>  
  &运算通常用于二进制取位操作,例如一个数 & 1 的结果就是取二进制的最末位。还可以判断奇偶x&1==0为偶,x&1==1为奇。
  >>运算比较单纯,二进制去掉最后一位,不多说了,先放代码再解释
 1 int poww(int a,int b){
 2     int ans=1,base=a;
 3     while(b!=0){
 4         if(b&1!=0)
 5           ans*=base;
 6         base*=base;
 7         b>>=1;
 8   }
 9     return ans;
10 }

代码很短,死记也可行,但最好还是理解一下吧,其实也很好理解,以b==11为例,b=>1011,二进制从右向左算,但乘出来的顺序是 a^(2^0)*a^(2^1)*a^(2^3),是从左向右的。我们不断的让base*=base目的即是累乘,以便随时对ans做出贡献。

  其中要理解base*=base这一步,看:::base*base==base^2,下一步再乘,就是base^2*base^2==base^4,然后同理  base^4*base4=base^8,,,,,see?是不是做到了base-->base^2-->base^4-->base^8-->base^16-->base^32.......指数正是 2^i 啊,再看上  面的例子,a¹¹= a^(2^0)*a^(2^1)*a^(2^3),这三项是不是完美解决了,,嗯,快速幂就是这样。

  顺便啰嗦一句,由于指数函数是爆炸增长的函数,所以很有可能会爆掉int的范围,根据题意决定是用 long long啊还是unsigned int啊还是mod某个数啊自己看着办。

 

 1 #include<iostream>
 2 #include<cmath>
 3 #include<cstdio> 
 4 using namespace std;
 5 int fastpow(int a,int b)
 6 {
 7     int r=1;
 8     int step=a;
 9     while(b!=0)
10     {
11         if(b%2!=0)
12         r=r*step;
13         step=step*step;
14         b=b/2;
15     }
16     return r;
17 }
18 int main()
19 {
20     int a,b;
21     scanf("%d%d",&a,&b);
22     int ans=fastpow(a,b);
23     printf("%d",ans);
24     return 0;
25 }
快速幂

 

六丶排列数

(1)基本排列

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 int f(int n)
 6 {
 7     if(n==0)return 1;
 8     else return n*f(n-1);
 9 }
10 int main()
11 {
12     int a,b;
13     scanf("%d%d",&a,&b);
14     printf("%d",f(a)/f(a-b));
15     return 0;
16 }
基本排列

 

 

(2)全排列

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 int f(int n)
 6 {
 7     if(n==0)return 1;
 8     else return n*f(n-1);
 9 }
10 int main()
11 {
12     int a,b;
13     scanf("%d%d",&a,&b);
14     printf("%d",f(a));
15     return 0;
16 }
全排列

 

 

(3)圆排列

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 int f(int n)
 6 {
 7     if(n==0)return 1;
 8     else return n*f(n-1);
 9 }
10 int main()
11 {
12     int a,b;
13     scanf("%d%d",&a,&b);
14     printf("%d",f(a)/(f(a-b)*b));
15     return 0;
16 }
圆排列

 

(4)项链排列

 1 #include<iostream>
 2 #include<cmath>
 3 #include<cstdio>
 4 using namespace std;
 5 int f(int n)
 6 {
 7     if(n==0)return 1;
 8     else return n*f(n-1);
 9 }
10 int main()
11 {
12     int a;
13     scanf("%d",&a);
14     if(a==1||a==2)printf("1");
15     else printf("%d",f(a-1)/2);
16     return 0;
17 }
项链排列

 

七丶组合数

求组合数

 

 

 

posted @ 2017-04-20 20:49  zzzzx  阅读(350)  评论(0编辑  收藏  举报