数学问题的解题窍门


辗转相除法

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 }
    gcd

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 }
    exgcd
  • 补充:在求得特解之后,如何求出其它所有整数解呢?对于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 }

猜你喜欢

转载自www.cnblogs.com/Ymir-TaoMee/p/9463743.html