基本说明

由于数学类方法经常性混合使用(比如组合数+快速幂),因此例题在最后统一给出。


一、最大公约数和最小公倍数(gcd)

1. 定义

没啥好说,大家都懂。最大公约数 gcd(a,b) ,最小公倍数 lcm(a,b) 。

2. 解析

求最大公约一般用辗转相除法,最小公倍数可以由最大公约数导出:

图片说明

同时注意为了防止a*b溢出,最终我们最小公倍数用下述公式求解:

图片说明

3. 模板

LL gcd(LL a, LL b) {
    return b == 0 ? a : gcd(b, a % b);
}

二、扩展欧几里得算法(exgcd)

1. 定义

上面说的求最大公约数的方式实际上叫欧几里得算法,而这里要讲的是它的升级版,不仅可以计算出前面说的最小公约数,还可以求出相应的“不定方程解”.

2. 解析

exgcd可以快速求出下面方程中的x、y、gcd(a,b):

图片说明

上面的公式为裴蜀定理,有很多有用的结论:

  • 上述式子一定有整数解,通过exgcd可以求出其中一组整数解
  • 若要求出其它解,只需要 x+=b/gcd(a,b),y-=a/gcd(a,b) 即可
    • 注意不要漏了除以 gcd(a, b)
    • +、-可以互换,相当于从另一个方向找其它解
  • 对于ax+by=c,若c不是gcd(a, b)的倍数,则该方程无解
  • ......

3. 模板

注意 在求解 ax+by=c 时要特判 a、b等于0 的情况

LL exgcd(LL a, LL b, ll& x, ll& y) {
    if(b == 0) {
        y = 0, x = 1;
        return a;
    }
    ll res = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return res;
}

三、快速幂算法

1. 定义

一种用来快速求解指数很大的幂运算的算法。

2. 解析

原理是将幂次减半,底数平方,并以此类推直到幂次为0。比如3^10=9^5=(9^4)(9^1)=(81^2)(9^1)=(6561^1)(9^1),这样原式的答案就是6561*9了。

3. 模板

  • 普通版,注意用long long:

    LL fast_pow(LL a, LL b) {  // 快速幂算法
      LL res = 1;
      while(b) {
          if(b & 1) res = res * a;  // 指数为奇时分一个出来
          b >>= 1;  // 指数减半
          a = a * a;  // 底数平方
      }
      return res;
    }
  • 带mod版,注意用long long:

    LL fast_pow_mod(LL a, LL b) {  // 快速幂算法 带mod版
      LL res = 1;
      while(b) {
          if(b & 1) res = res * a % mod;
          b >>= 1;
          a = a * a % mod;
      }
      return res % mod;
    }

四、除法求模(逆元)

1. 定义

我们都知道,对于加法、减法、乘法,求模运算都可以提前,即:

(a + b) % p = (a % p + b % p) % p
(a - b) % p = (a % p - b % p ) % p
(a * b) % p = (a % p * b % p) % p
而除法求模不行,因此对于除法求模我们需要使用一种专门的方法,即转化为乘法求模。

2. 解析

由费马小定理:

图片说明

转化一下则有:

图片说明

也就是说,对于除以b取模可以转化为乘以b^(m-2)取模! 这样就成功将除法取模转化为了乘法取模。但要注意b、m必须互质。好消息是,这里的m一般就是题目给出的mod值,一般题目给出的都是一个很大的质数。

因此我们一般称b^(m-2)为b的 逆元 ,除以b取模相当于乘以b的逆元取模。
而对于b^(m-2)的求法,参见前文的 快速幂算法 即可。

3. 模板

以求组合数的模为例,里面要用到除法(除数为阶乘)求模:

int C(int n, int m) {  // n! / (n-m)! / m! => n! * [(n-m)!]^(mod-2) * [m!]^(mod-2)
    return fac[n] * inv_fac[n - m] % mod * inv_fac[m] % mod;
}

四、指数求模

1. 定义

指数比较大的情况一般用快速幂可以解决,然而....

有时用快速幂还是会超时....这个时候也需要将指数模一模.

2. 解析

由费马小定理:

图片说明

将k组上式乘到下面这个式子中

图片说明

得到

图片说明

上面两个式子说明,可以对指数 模(m-1),答案不会变化。

3. 结论

在计算指数运算时,要是指数太大,可以 模(m-1),答案不会变化。m为题目指定的mod。


五、组合数(C(n, m))

1. 定义

在一些题目中需要用到组合数,甚至通过组合数直接输出结果,因此有必要学习一下组合数的计算方式。
图片说明

2. 解析

两种方法

  • 方法一:通过动态规划预处理出C[maxn][maxn]数组,预处理用时O(n^2),查询用时O(1)
    一般只要预处理不会超时、空间不会爆 就优先用这个!!
    图片说明

  • 方法二:仅预处理出阶乘数fac[maxn]和阶乘数的逆元inv_fac[maxn],预处理时间O(n*lg(mod)),查询用时稍微慢一点的O(1)
    若方法一预处理超时了 或 内存爆了 就用这个。

图片说明

3. 模板

  • 方法一
void init(ll n) {
    C[0][0] = 1;
    for(ll i = 1; i <= n; i ++) {
        for(ll j = 0; j <= i; j ++) {
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
        }
    }
}
  • 方法二
LL fast_pow_mod(LL a, LL b) {  // 快速幂
    LL res = 1;
    while(b) {
        if(b & 1) res = res * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return res % mod;
}

void init(int n) {  // 预处理
    fac[0] = inv_fac[0] = 1;
    for(int i = 1; i <= n; i ++) {
        fac[i] = fac[i - 1] * i % mod;
        inv_fac[i] = fast_pow_mod(fac[i], mod - 2);
    }
}

int C(int n, int m) {  // 计算组合数
    return fac[n] * inv_fac[n - m] % mod * inv_fac[m] % mod;
}

Fin. 例题

Nowcoder 数列统计:https://blog.nowcoder.net/n/44393c613d934015bd7c7a54888535a0
Nowcoder 子序列:(题目链接)https://ac.nowcoder.com/acm/contest/7539/F
Nowcoder 火柴排队:(题目链接)https://ac.nowcoder.com/acm/contest/7329/D