辗转相除法
1.求最大公约数
- 问题:线段上格点的个数
- 问题描述:给定平面上的两个格点P1=(x1,y1)和P2=(x2,y2),线段P1P2上,除P1和P2以外一共有几个格点?
- 限制条件:-109≤x1,y1,x2,y2≤109
- 分析:答案显然,是|x1-x2|和|y1-y2|的最大公约数-1。那么问题的关键就是求最大公约数,用辗转相除法就可以了。
辗转相除法的原理:①a=b*p+q,所以gcd(b,q)既整除a又整除b,也就整除gcd(a,b)(公约数整除最大公约数)②q=a-b*p,同理可证gcd(a,b)整除gcd(b,q)。综上有gcd(a,b)=gcd(b,a%b)。不断这样操作下去,由于gcd的第二个参数总是不断减小的,最后会出现gcd(a,b)=gcd(c,0),而0和c的最大公约数就是c,所以gcd(c,0)=c,这样就计算出了gcd(a,b) - 代码:
1 #include <cstdio> 2 #include <cctype> 3 #include <cmath> 4 #define num s-'0' 5 using namespace std; 6 7 int x1,x2,y1,y2; 8 9 void read(int &x){ 10 char s; 11 x=0; 12 bool flag=0; 13 while(!isdigit(s=getchar())) 14 (s=='-')&&(flag=true); 15 for(x=num;isdigit(s=getchar());x=x*10+num); 16 (flag)&&(x=-x); 17 } 18 19 void write(int x) 20 { 21 if(x<0) 22 { 23 putchar('-'); 24 x=-x; 25 } 26 if(x>9) 27 write(x/10); 28 putchar(x%10+'0'); 29 } 30 31 int gcd(int, int); 32 33 int main() 34 { 35 read(x1);read(y1);read(x2);read(y2); 36 write(gcd(abs(x1-x2),abs(y1-y2))-1); 37 } 38 39 int gcd(int x, int y) 40 { 41 if (y==0) return x; 42 return gcd(y, x%y); 43 }
2.扩展欧几里得算法
- 问题描述:求整数x和y使得ax+by=1
- 限制条件:1≤a,b≤109
- 分析:如果gcd(a,b)≠1,显然无解,反之,如果gcd(a,b)=1,就可以通过扩展辗转相除法来求解。事实上一定存在整数对(x,y)使得ax+by=gcd(a,b),并可以用同样的算法求得。
扩展欧几里得原理:要求ax+by=gcd的整数解x,y,假设已经求得了bx'+(a%b)y'=gcd的整数解x'和y',再将a%b=a-(a/b)*b带入,有ay'+b(x'-(a/b)*y')=gcd,于是我们就得到了(x,y)和(x',y')之间的关系:x=y',y=x'-(a/b)*y'。而当b=0时,gcd=a,此时有x=1,y=0.通过类似于辗转相除法的递归过程,不断地迭代,可得到ax+by=gcd的一个正整数解,我们可将它称为特解,同时得到gcd的值。 - 代码:
1 #include <cstdio> 2 #include <cctype> 3 #include <cmath> 4 #define num s-'0' 5 using namespace std; 6 7 int a,b,x,y; 8 9 void read(int &x){ 10 char s; 11 x=0; 12 bool flag=0; 13 while(!isdigit(s=getchar())) 14 (s=='-')&&(flag=true); 15 for(x=num;isdigit(s=getchar());x=x*10+num); 16 (flag)&&(x=-x); 17 } 18 19 void write(int x) 20 { 21 if(x<0) 22 { 23 putchar('-'); 24 x=-x; 25 } 26 if(x>9) 27 write(x/10); 28 putchar(x%10+'0'); 29 } 30 31 int exgcd(int, int, int&, int&); 32 33 int main() 34 { 35 read(a);read(b); 36 write(exgcd(a,b,x,y)); 37 putchar('\n'); 38 write(x); 39 putchar(' '); 40 write(y); 41 } 42 43 int exgcd(int a, int b, int &x, int &y) 44 { 45 int d=a; 46 if (b != 0) 47 { 48 d=exgcd(b, a%b, y, x); 49 y-=(a/b)*x; 50 } 51 else 52 { 53 x=1; y=0; 54 } 55 return d; 56 }
- 补充:在求得特解之后,如何求出其它所有整数解呢?对于ax+by=gcd,让x增加(减少)b/gcd,让y减少(增加)a/gcd。让x增加b/gcd,对于ax这一项来说,增加了a*b/gcd,可以看出来这是a,b的最小公倍数,同理让y减少a/gcd,对于by这一项来说,也就减少了a,b的最小公倍数,这样加起来两项的和仍然是gcd。求其通解在求逆元时会有所涉及。
有关素数的基本算法
1.素数测试
- 问题描述:素数判定,给定整数n,判断n是不是素数。
- 限制条件:1≤n≤109
- 分析:首先,1不是素数,当n不等于1时,由于n的约数不超过n,且素数要求除1和n外无其他约数,所以把检查范围确定在2~n-1,在这个基础上,如果d是n的约数,那么n/d也是n的约数,由min(d,n/d)≤√n,所以只要检查2~√n就够了。同理可知,整数分解和约数枚举都可以在O(√n)时间内完成。虽然还有更高效的费马测试,ρ算法,数域筛法等,不过大多数情况下这已经足够了。
- 代码:
1 #include <cstdio> 2 #include <cctype> 3 #include <vector> 4 #include <map> 5 #define num s-'0' 6 using namespace std; 7 8 int n; 9 10 void read(int &x){ 11 char s; 12 x=0; 13 bool flag=0; 14 while(!isdigit(s=getchar())) 15 (s=='-')&&(flag=true); 16 for(x=num;isdigit(s=getchar());x=x*10+num); 17 (flag)&&(x=-x); 18 } 19 20 void write(int x) 21 { 22 if(x<0) 23 { 24 putchar('-'); 25 x=-x; 26 } 27 if(x>9) 28 write(x/10); 29 putchar(x%10+'0'); 30 } 31 32 bool is_prime(int n);//素性测试 33 vector<int> divisor(int);//约数枚举 34 map<int, int> prime_factor(int);//整数分解 35 36 int main() 37 { 38 read(n); 39 if (is_prime(n)) puts("true"); 40 else puts("false"); 41 vector<int> v=divisor(n); 42 map<int, int> m=prime_factor(n); 43 for (int i=0; i<v.size(); i++) 44 { 45 write(v[i]);putchar(' '); 46 } 47 putchar('\n'); 48 for (map<int, int>::iterator ite=m.begin(); ite!=m.end(); ite++) 49 { 50 write(ite->first);printf(": ");write(ite->second);putchar('\n'); 51 } 52 } 53 54 bool is_prime(int n)//素性测试 55 { 56 for (int i=2; i*i<=n; i++) 57 { 58 if (n%i==0) return false; 59 } 60 return n!=1;//1是个例外 61 } 62 63 vector<int> divisor(int n)//约数枚举 64 { 65 vector<int> res; 66 for (int i=1; i*i<=n; i++) 67 { 68 if (n%i==0) 69 { 70 res.push_back(i); 71 if (i!=n/i) res.push_back(n/i); 72 } 73 } 74 return res; 75 } 76 77 map<int, int> prime_factor(int n)//整数分解 78 { 79 map<int, int> res; 80 for (int i=2; i*i<=n; i++) 81 { 82 while (n%i==0) 83 { 84 ++res[i]; 85 n/=i; 86 } 87 } 88 if (n>1) ++res[n]; 89 return res; 90 }
2.埃氏筛法
- 问题描述:给定整数n,求n以内的素数
- 限制条件:n≤106
- 分析:将2~n范围内的整数写下,其中最小的数字2是素数,将表中所有2的倍数都划去,表中剩余的最小数字是3,它不能被更小的数整除,所以是素数,再将表中所有3的倍数都划去,以此类推,如果表中剩余的最小数字是m,那m就是素数,然后将所有m的倍数都划去,像这样反复操作,就可以得到n以内的素数
- 代码:
1 #include <cstdio> 2 #include <cctype> 3 #include <algorithm> 4 #define num s-'0' 5 using namespace std; 6 7 const int MAX_N=1000000; 8 int n; 9 bool is_prime[MAX_N+1]; 10 int prime[MAX_N]; 11 int p=0; 12 13 void read(int &x){ 14 char s; 15 x=0; 16 bool flag=0; 17 while(!isdigit(s=getchar())) 18 (s=='-')&&(flag=true); 19 for(x=num;isdigit(s=getchar());x=x*10+num); 20 (flag)&&(x=-x); 21 } 22 23 void write(int x) 24 { 25 if(x<0) 26 { 27 putchar('-'); 28 x=-x; 29 } 30 if(x>9) 31 write(x/10); 32 putchar(x%10+'0'); 33 } 34 35 int sieve(int); 36 37 int main() 38 { 39 read(n); 40 write(sieve(n));putchar('\n'); 41 for (int i=1; i<=p; i++) 42 { 43 write(prime[i]);putchar(' '); 44 if (i%10==0) putchar('\n'); 45 } 46 } 47 48 int sieve(int n) 49 { 50 fill(is_prime, is_prime+(n+1), true); 51 fill(prime, prime+n, 0); 52 is_prime[0]=is_prime[1]=false; 53 for (int i=2; i<=n; i++) 54 { 55 if (is_prime[i]) 56 { 57 prime[++p]=i; 58 for (int j=2*i; j<=n; j+=i) 59 is_prime[j]=false; 60 } 61 } 62 return p; 63 }
埃氏筛法的复杂度O(nloglogn) ,可近似看成线性的,下面补充一种线性筛法
补充:欧拉筛法
- 欧拉筛法原理:埃氏筛法没有达到线性是因为它对于同一个合数,重复操作它的质因子个数次(即:它的每一个质因子都会筛掉它一次),欧拉筛法对此做了改进,其原理基于这样的事实:由于任何一个合数都可以表示成一个质数和一个数的乘积,对于一个可表示为合数和质数乘积的数,它有可能能用更大的合数和更小的质数的乘积来表示。在筛数的过程中,我们让每一个合数,都由它最小的素因子筛掉,这样就不会有重复筛同一个合数的情况出现了。那如何实现呢?埃氏筛法是在找到一个素数p之后,将n以内p的倍数全部筛去,而欧拉筛法不是,欧拉筛法将外层循坏的每一个i和已找到的素数分别相乘,效果上看,其实是一样的,但仅仅这样不能提高效率,欧拉筛最核心的地方是,当i和已经找到的某个素数pk满足i%pk=0时break,下面说明这样的做的合理性,不妨假设i/pk=t,如不跳出,则会将i*pk+1筛去,而i=t*pk,且pk<pk+1,所以i*pk+1=t*pk*pk+1=(t*pk+1)*pk,故i*pk+1可在i循坏到(t*pk+1)时,由pk筛去,对于pk+2等往后的素数,同理。从而这样就保证每个合数都由最小的素因子筛去。
- 代码:
1 #include <cstdio> 2 #include <cctype> 3 #include <algorithm> 4 #define num s-'0' 5 using namespace std; 6 7 const int MAX_N=1000000; 8 int n; 9 bool is_prime[MAX_N+1]; 10 int prime[MAX_N]; 11 int p=0; 12 13 void read(int &x){ 14 char s; 15 x=0; 16 bool flag=0; 17 while(!isdigit(s=getchar())) 18 (s=='-')&&(flag=true); 19 for(x=num;isdigit(s=getchar());x=x*10+num); 20 (flag)&&(x=-x); 21 } 22 23 void write(int x) 24 { 25 if(x<0) 26 { 27 putchar('-'); 28 x=-x; 29 } 30 if(x>9) 31 write(x/10); 32 putchar(x%10+'0'); 33 } 34 35 int sieve(int); 36 37 int main() 38 { 39 read(n); 40 write(sieve(n));putchar('\n'); 41 for (int i=1; i<=p; i++) 42 { 43 write(prime[i]);putchar(' '); 44 if (i%10==0) putchar('\n'); 45 } 46 } 47 48 int sieve(int n) 49 { 50 fill(is_prime, is_prime+(n+1), true); 51 fill(prime, prime+n, 0); 52 is_prime[0]=is_prime[1]=false; 53 for (int i=2; i<=n; i++) 54 { 55 if (is_prime[i]) 56 { 57 prime[++p]=i; 58 } 59 for (int j=1; j<=p; j++) 60 { 61 if (prime[j]>n/i) break; 62 is_prime[i*prime[j]]=false; 63 if (i%prime[j]==0) break; 64 } 65 } 66 return p; 67 }
补充:欧拉函数
- 欧拉函数:对于正整数N,少于或等于N且与N互质的正整数(包括1)的个数,记作φ(n)。
-
φ(x)=x*(1-1/p1)*(1-1/p2)*(1-1/p3)*…*(1-1/pn) 其中p1,p2…pn为x的所有质因数;x是正整数;
φ(1)=1(唯一和1互质的数,且小于等于1)。
注意:每种质因数只有一个 - 性质: ①若n是素数p的k次幂,则φ(n)=pk-pk-1=(p-1)pk-1,因为除了p的倍数外,其他数都跟n互质
特殊地,若n是素数p,则φ(p)=p-1
②若m,n互质,则φ(mn)=φ(m)φ(n) ,所以欧拉函数是积性函数
特殊地,当n是奇数,φ(2n)=φ(n) - 有关欧拉函数的求法:
①利用积性和欧拉筛法的思想,求出1~n所有数的欧拉函数值1 #include <cstdio> 2 #include <cctype> 3 #include <algorithm> 4 #define num s-'0' 5 using namespace std; 6 7 const int MAX_N=1000000; 8 int n; 9 bool is_prime[MAX_N+1]; 10 int prime[MAX_N]; 11 int p=0; 12 int phi[MAX_N+1]; 13 14 void read(int &x){ 15 char s; 16 x=0; 17 bool flag=0; 18 while(!isdigit(s=getchar())) 19 (s=='-')&&(flag=true); 20 for(x=num;isdigit(s=getchar());x=x*10+num); 21 (flag)&&(x=-x); 22 } 23 24 void write(int x) 25 { 26 if(x<0) 27 { 28 putchar('-'); 29 x=-x; 30 } 31 if(x>9) 32 write(x/10); 33 putchar(x%10+'0'); 34 } 35 36 void euler(int); 37 38 int main() 39 { 40 read(n); 41 euler(n);putchar('\n'); 42 for (int i=1; i<=n; i++) 43 { 44 write(phi[i]);putchar(' '); 45 if (i%10==0) putchar('\n'); 46 } 47 } 48 49 void euler(int n) 50 { 51 fill(is_prime, is_prime+(n+1), true); 52 fill(prime, prime+n, 0); 53 fill(phi, phi+(n+1), 0); 54 is_prime[0]=is_prime[1]=false; 55 phi[1]=1; 56 for (int i=2; i<=n; i++) 57 { 58 if (is_prime[i]) 59 { 60 prime[++p]=i; 61 phi[i]=i-1; 62 } 63 for (int j=1; j<=p; j++) 64 { 65 if (prime[j]>n/i) break; 66 is_prime[i*prime[j]]=false; 67 if (i%prime[j]==0) 68 { 69 phi[i*prime[j]]=phi[i]*prime[j]; 70 break; 71 } 72 else 73 { 74 phi[i*prime[j]]=phi[i]*phi[prime[j]]; 75 } 76 } 77 } 78 }
②直接求φ(n),利用公式
1 #include <cstdio> 2 #include <cctype> 3 #include <algorithm> 4 #define num s-'0' 5 using namespace std; 6 7 const int MAX_N=1000000; 8 int n; 9 10 void read(int &x){ 11 char s; 12 x=0; 13 bool flag=0; 14 while(!isdigit(s=getchar())) 15 (s=='-')&&(flag=true); 16 for(x=num;isdigit(s=getchar());x=x*10+num); 17 (flag)&&(x=-x); 18 } 19 20 void write(int x) 21 { 22 if(x<0) 23 { 24 putchar('-'); 25 x=-x; 26 } 27 if(x>9) 28 write(x/10); 29 putchar(x%10+'0'); 30 } 31 32 int phi(int); 33 34 int main() 35 { 36 read(n); 37 write(phi(n));putchar('\n'); 38 } 39 40 int phi(int n) 41 { 42 int res=n; 43 for (int i=2; i*i<=n; i++) 44 { 45 if (n%i==0) 46 { 47 res=res*(i-1)/i; 48 while (n%i==0) n=n/i; 49 } 50 } 51 if (n>1) res=res*(n-1)/n; 52 return res; 53 }
3.区间筛法
- 问题描述:给定整数a和b,求[a,b)内有多少素数
- 限制条件:
a<b≤1012
b-a≤106 - 分析:因为b以内的合数的最小质因数一定不超过√b,如果有√b以内的素数表的话,就可以把埃氏筛法用在[a,b)上了,先分别做好[2,√b)的表和[a,b)的表,然后从[2,sqrt(b))的表中筛得素数的同时,也将其倍数从[a,b)的表中划去,最后剩下的就是区间[a,b)内的素数了。这里还有一点需要注意的是:由于b的数值很大,都已经超过了int范围,如果直接开1~b的数组的话,会造成相当大的空间浪费,因此,需要做一个数组的下标偏移。
- 代码:
1 #include <cstdio> 2 #include <cctype> 3 #include <algorithm> 4 #define num s-'0' 5 using namespace std; 6 7 const int MAX_L=1000000; 8 long long a,b; 9 bool is_prime_small[MAX_L]; 10 bool is_prime[MAX_L]; 11 12 int max(int x, int y) 13 { 14 if (x>y) return x; 15 return y; 16 } 17 18 void read(long long &x){ 19 char s; 20 x=0; 21 bool flag=0; 22 while(!isdigit(s=getchar())) 23 (s=='-')&&(flag=true); 24 for(x=num;isdigit(s=getchar());x=x*10+num); 25 (flag)&&(x=-x); 26 } 27 28 void write(long long x) 29 { 30 if(x<0) 31 { 32 putchar('-'); 33 x=-x; 34 } 35 if(x>9) 36 write(x/10); 37 putchar(x%10+'0'); 38 } 39 40 void segment_sieve(); 41 42 int main() 43 { 44 read(a);read(b); 45 segment_sieve(); 46 for (int i=0; i<b-a; i++) 47 { 48 if (is_prime[i] && i+a!=0 && i+a!=1) 49 { 50 write(i+a); 51 putchar(' '); 52 } 53 } 54 } 55 56 void segment_sieve() 57 { 58 fill(is_prime_small, is_prime_small+b, true); 59 fill(is_prime, is_prime+(b-a), true); 60 for (int i=2; (long long)i*i<b; i++) 61 { 62 if (is_prime_small[i]) 63 { 64 for (int j=2*i; (long long)j*j<b; j+=i) 65 is_prime_small[j]=false; 66 for (long long j=max(2LL, (a+i-1)/i)*i; (long long)j<b; j+=i) 67 is_prime[j-a]=false; 68 } 69 } 70 }
快速幂
O(logn)时间内完成幂运算
两种写法:
①将n拆解成2的幂次
1 #include <cstdio> 2 #include <cctype> 3 #include <algorithm> 4 #define num s-'0' 5 using namespace std; 6 7 long long x,n,mod; 8 9 void read(long long &x){ 10 char s; 11 x=0; 12 bool flag=0; 13 while(!isdigit(s=getchar())) 14 (s=='-')&&(flag=true); 15 for(x=num;isdigit(s=getchar());x=x*10+num); 16 (flag)&&(x=-x); 17 } 18 19 void write(long long x) 20 { 21 if(x<0) 22 { 23 putchar('-'); 24 x=-x; 25 } 26 if(x>9) 27 write(x/10); 28 putchar(x%10+'0'); 29 } 30 31 long long mod_pow(long long, long long, long long); 32 33 int main() 34 { 35 read(x);read(n);read(mod); 36 write(mod_pow(x,n,mod)); 37 putchar(' '); 38 } 39 40 long long mod_pow(long long x, long long n, long long mod) 41 { 42 long long res=1; 43 while (n>0) 44 { 45 if (n & 1) res=res*x % mod; 46 x=x*x%mod; 47 n >>= 1; 48 } 49 return res; 50 }
②递归求解
1 #include <cstdio> 2 #include <cctype> 3 #include <algorithm> 4 #define num s-'0' 5 using namespace std; 6 7 long long x,n,mod; 8 void read(long long &x){ 9 char s; 10 x=0; 11 bool flag=0; 12 while(!isdigit(s=getchar())) 13 (s=='-')&&(flag=true); 14 for(x=num;isdigit(s=getchar());x=x*10+num); 15 (flag)&&(x=-x); 16 } 17 18 void write(long long x) 19 { 20 if(x<0) 21 { 22 putchar('-'); 23 x=-x; 24 } 25 if(x>9) 26 write(x/10); 27 putchar(x%10+'0'); 28 } 29 30 long long mod_pow(long long, long long, long long); 31 32 int main() 33 { 34 read(x);read(n);read(mod); 35 write(mod_pow(x,n,mod)); 36 putchar(' '); 37 } 38 39 long long mod_pow(long long x, long long n, long long mod) 40 { 41 if (n==0) return 1; 42 long long res=mod_pow(x*x%mod, n/2, mod); 43 if (n & 1) res=res*x%mod; 44 return res; 45 }