【PAT】2. 数学问题

【PAT】2. 数学问题

最大公约数与最小公倍数

求解最大公约数

欧几里得算法:设a,b均为正整数,则gcd(a,b)=gcd(b,a%b)gcd(a,b)=gcd(b,a\%b)gcd(a,b)=gcd(b,a%b),gcd(a,b)来表示a和b的最大公约数

递归边界为gcd(a,0) = a,零和任何一个整数a的最大公约数都是a

int gcd(int a, int b){
    if(b == 0) return a;
    else return gcd(b,a % b);
}

或者

int gcd(int a, int b){
    return !b ? a : gcd(b, a % b);
}

求解最小公倍数

一般用lcm(a, b)来表示a和b的最小公倍数,在求得a和b的最大公约数d之后,可以得到a和b的最小公倍数是ab/d,可以借助集合来理解这个公式。因为a*b可能会溢出,所以一般写成a/d*b

分数的四则运算

分数的表示

struct Fraction{//分数
    int up, dpwn;//分子,分母
};

对分数的这种表示指定三项规则:

  1. 使down为非负数。如果分数为负,那么令分子up为负即可
  2. 如果该分数恰为0,那么规定其分子为0,分母为1
  3. 分子和分母没有除了1以外的公约数

分数的化简:

  1. 如果分母down为负数,那么令分子up和分母down都变为相反数(使down为非负数)
  2. 如果分子up为0,那么令分母down为1
  3. 约分:求出分子绝对值与分母绝对值的最大公约数d,然后令分子分母同时除以d

分数的输出:

  1. 输出分数前要对其化简
  2. 如果分数r的分母down为1,说明是整数,直接输出分子
  3. 如果分数r的分子up绝对值大于分母down,说明分数是假分数,应按带分数的形式输出,即整数部分为r.up/r.down,分子部分为abs(r.up)%r.down,分母部分为r.down

注意:由于分数的乘法和除法过程中可能使分子或分母超过int型表示范围,因此一般情况下分子和分母使用long long型存储

素数

判断n是否是素数:判定它是否能被2,3,...,⌊sqrt(n)⌋2,3,...,\left \lfloor sqrt(n) \right \rfloor2,3,...,sqrt(n)中的一个整除,时间复杂度为O(sqrt(n))O(sqrt(n))O(sqrt(n))

从1~n进行枚举,判断每个数是否是素数,就能获取一个素数表,时间复杂度为O(nn)O(n\sqrt{n})O(nn)

更高效的埃氏筛法时间复杂度为O(nloglogn)O(nloglogn)O(nloglogn),关键在于“筛”。算法从小到大枚举所有数,对每一个素数,筛取它的所有倍数剩下的就都是素数。

假设从小到大到达某数a时,a没有被前面的步骤筛去,那么a一定是素数。因为如果a不是素数,那么a一定有小于a的素因子,这样在之前的步骤中a一定会被筛掉。

质因子分解

质因子分解是指将一个正整数n写成一个或者多个质数的乘积的形式。

每个质因子可以出现不止一次,因此定义结构体factor,存放质因子及其个数

struct factor{
    int x, cnt; //x为质因子,cnt为其个数
}fac[10];

考虑到2*3*5*7*11*13*17*19*23*29已经超过了int范围,因此对一个int型的数来说,fac数组大小只需要开到10就可以了

核心原理:对整数n来说,如果它存在[2,n]范围内的质因子,要么这些质因子全部小于等于sqrt(n),要么只存在一个大于sqrt(n)的质因子,而其余质因子全部小于等于sqrt(n)。算法时间复杂度为O(n)O(\sqrt{n})O(n)

  1. 枚举1~sqrt(n)内的所有质因子p,判断p是否是n的因子
    • 如果p是因子,给fac数组中增加质因子p,初始化其个数为0.然后只要p还是n的因子,就让n不断除以p,每次操作令p的个数加1,直到p不再是n的因子为止
    • 如果p不是因子,则跳过。
      if(n % prime[i] == 0){
          fac[num].x = n;
          fac[num].cnt = 0;
          while(n % prime[i] == 0){
              fac[num].cnt++;
              n /= prime[i];
          }
          num++;
      }
      
  2. 如果上面步骤结束后n仍然大于1,说明n有且仅有一个大于srqt(n)的质因子(有可能是n本身),这时将这个质因子加入fac数组,并令其个数为1.
    if(n != 1){
        fac[num].x = n;
        fac[num++].cnt = 1;
    }
    
大整数存储

按顺位存储:整数的高位存储在数组的高位,整数的低位存储在数组的低位。如整数235813,有d[0]=3,d[1]=1.d[2]=8,d[3]=5,d[4]=3,d[5]=2。原因是进行运算的时候都是从整数的低位到高位进行枚举。因此注意当把整数按字符串%s读入的时候,实际上是逆位存储的,即str[0]=‘2’,str[1]=‘3’,…,str[5]=‘3’,因此在读入后需要在另存至d[]数组的时候反转一下。

扩展欧几里得算法
  • 扩展欧几里得算法
  • 方程ax+by=c的求解
  • 同余式ax=c(mod m)的求解
  • 逆元的求解
组合数

求n!中有多少个质因子p

直观的想法是计算1~n中的每个数各有多少个质因子p,然后将结果累加,时间复杂度为O(nlogn)

由 n!中有(np+np2+np3+⋯ )\left ( \frac{n}{p}+\frac{n}{p^2}+\frac{n}{p^3}+\cdots \right )(pn+p2n+p3n+)个质因子p,可以得到O(logn)O(logn)O(logn)的算法

//计算n!中有多少质因子p
int cal(int n, int p){
    int ans = 0;
    while(n){
        ans += n/p;//累加n/p^k
        n /= p;
    }
    return ans;
}

利用这个算法可以很快计算出n!的末尾有多少零:由于末尾0的个数等于n!中因子10的个数,这又等于n!中质因子5的个数,只需要带入cal(n,5)即可求出结果。

n!中质因子p的个数,实际上等于1~n中p的倍数的个数n/p加上(n/p)!中质因子p的个数,递归代码如下

int cal(int n, int p){
    if(n < p) return 0;
    return n / p + cal(n / p, p);
}

组合数的计算

计算CnmC^m_nCnm

通过递推公式计算:Cnm=Cn−1m+Cn−1m−1C_{n}^{m} = C_{n-1}^{m} +C _{n-1}^{m-1}Cnm=Cn1m+Cn1m1,递归终点为Cn0=Cnn=1C_{n}^{0} = C_{n}^{n}=1Cn0=Cnn=1

long long res[67][67] = {0};
long long C(long long n, long long m){
    if(m = 0|| m = n) return 1;
    if(res[n