关于数论的整理
一丶扩展欧几里得算法
欧几里得算法是 辗转相除法?一种求两个数的最大公约数的算法: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
ax≡b(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 }
模板:
三丶 斐波那契数列
通项公式:
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时
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 }
七丶组合数