zoukankan      html  css  js  c++  java
  • 数学问题的解题窍门


    辗转相除法

    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('
      ');
      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('
      ');
      48     for (map<int, int>::iterator ite=m.begin(); ite!=m.end(); ite++)
      49     {
      50         write(ite->first);printf(": ");write(ite->second);putchar('
      ');
      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('
      ');
      41     for (int i=1; i<=p; i++)
      42     {
      43         write(prime[i]);putchar(' ');
      44         if (i%10==0) putchar('
      ');
      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('
      ');
      41     for (int i=1; i<=p; i++)
      42     {
      43         write(prime[i]);putchar(' ');
      44         if (i%10==0) putchar('
      ');
      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('
      ');
      42     for (int i=1; i<=n; i++)
      43     {
      44         write(phi[i]);putchar(' ');
      45         if (i%10==0) putchar('
      ');
      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('
      ');
      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 <algorithm>
       3 #include <cmath>
       4 
       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 void segment_sieve()
      13 {
      14     fill(is_prime_small, is_prime_small+(int)sqrt(b), true);
      15     fill(is_prime, is_prime+(b-a), true);
      16     for (int i=2; (long long)i*i<b; i++)
      17     {
      18         if (is_prime_small[i])
      19         {
      20             for (int j=2*i; (long long)j*j<b; j+=i) 
      21                 is_prime_small[j]=false;
      22             //((a+i-1)/i)*i为大于等于a的最小的i的倍数 
      23             for (long long j=max(2LL, (a+i-1)/i)*i; (long long)j<b; j+=i)
      24                 is_prime[j-a]=false;
      25         }
      26     }
      27 }
      28 
      29 int main()
      30 {
      31     scanf("%d %d", &a, &b);
      32     segment_sieve();
      33     for (int i=0; i<b-a; i++)
      34     {
      35         if (is_prime[i] && i+a!=0 && i+a!=1) 
      36         {
      37             printf("%d ", i+a);
      38         }
      39     }
      40 }
      区间筛法

    快速幂

    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 }
  • 相关阅读:
    codeforces 459C Pashmak and Buses(模拟,组合数A)
    HDU 4639 Hehe(字符串处理,斐波纳契数列,找规律)
    HDU 1671 Phone List(字符处理)
    网页爬虫【原创】【开源】
    asp.net mvc 配合前端js的CMD模块化部署思想,小思路
    [转]阎宏博士的JAVA与模式
    [转]使用设计模式改善程序结构(三)
    [转]使用设计模式改善程序结构(二)
    [转]使用设计模式改善程序结构(一)
    html符号转换
  • 原文地址:https://www.cnblogs.com/Ymir-TaoMee/p/9463743.html
Copyright © 2011-2022 走看看