杂谈 2:论编程中的简单数学

杂谈 2:论编程中的简单数学

1st 质数

因数只有 1 和数字本身的数称为质数。

素数计数函数:小于或等于 x 的素数的个数,用 π(x) 表示。随着 x 的增大,有这样的近似结果:π(x)xln(x)

素性测试:素性测试(Primality test)是一类在不对给定数字进行素数分解(prime factorization)的情况下,测试其是否为素数的算法。

那我们怎么判断一个数是不是质数呢?我们可以枚举 2x,对于每个 i,如果 xmodi=0,那么就不是质数。当然,x<2x 一定不是质数。

代码:

bool Prime(ll x) {
  if (x < 2) {
    return 0;
  }
  for (ll i = 2; i <= x; ++ i) {
    if (!(x % i)) {
      return 0;
    }
  }
  return 1;
}

可是,这样求解的时间复杂度是 O(x)x 太大直接爆炸。我们发现如果 in 的约数,那么 ni 也是 n 的约数(称 (i, ni)n 的约数对)。所以不必重复枚举 2 个,我们只要枚举最小的那个即可。由推理可得,所有 n 的约数对的最小数均 n。所以只需枚举 2x 即可。

代码:

bool Prime(ll x) {
  if (x < 2) {
    return 0;
  }
  for (ll i = 2; i * i <= x; ++ i) { // i * i <= x 等价于 i <= √x
    if (!(x % i)) {
      return 0;
    }
  }
  return 1;
}

那怎么一次性求出 1n 的所有数是不是质数呢?如果把每个数都检验一遍,那么时间复杂度为 O(nn),大概能撑到 n=2·105。下面介绍一种 O(n) 的算法:

找到一个质数后,就将它的倍数标记为合数,也就是把它的倍数筛掉,如果一个数没有被比它小的质数筛掉,那它就是质数。直到把 n 的质数全部筛完,程序结束。时间复杂度为 O(nloglogn)

代码:

const int kMaxN = 1e6 + 10; // 筛掉 1 ~ 10^6+10 的质数
bool isp[kMaxN + 1]; // 多开 1 个保险
bool InitPrime(ll x) {
  for (int i = 2; i * i <= kMaxN; ++ i) {
    if (!isp[i]) { // 如果 i 是质数
      for (int j = i * i; j <= kMaxN; j += i) { // 标记
        isp[j] = 1;
      }
    }
  }
}

2nd 最大公约数

最大公约数即为 Greatest Common Divisor,常缩写为 gcd。一组整数的最大公约数,是指所有公约数里面最大的一个。

2 个数的最大公约数:

考虑枚举 1min(a, b),找到最大的 iimoda=imodb=0,则 igcd(a, b) 的值。

代码:

ll GCD(ll a, ll b) {
  ll ans = 0;
  for (ll i = 1; i <= min(a, b); ++ i) {
    if (!(i % a) && !(i % b)) {
      ans = i;
    }
  }
  return ans;
}

但是,这个算法的时间复杂度是 O(min(a, b))ab 太大就会超时。设 a>b,我们发现如果 ba 的约数,那么 b 就是二者的最大公约数。下面讨论不能整除的情况,即 a=bq+rr<b)。我们可以得到 gcd(a, b)=gcd(b, amodb)

代码:

ll GCD(ll a, ll b) {
  if (!b) {
    return a;
  }
  return GCD(b, a % b);
}

但大整数(>264)取模的时间复杂度较高,而加减法时间复杂度较低。针对大整数,我们可以用更相减损术。设 ab,有:

gcd(a,b)={if a=baelse gcd(ab,b)

因为当 a=b 时,d | a,d | b,有 d | ab,否则可以证明 a,b 的所有公因数都是 ab, b 的公因数。

代码:

BigInt GCD(BigInt a, BigInt b) {
  int cnt1 = 0, cnt2 = 0;
  for (; !Mod(a, 2); ++ cnt1) { // Mod 为高精 a % b 函数
    RMove(a, 1); // a >>= 1 的高精版
  }
  for (; !Mod(b, 2); ++ cnt1) { // Mod 为高精 a % b 函数
    RMove(b, 1); // b >>= 1 的高精版
  }
  for (; ; ) {
    for (; !Mod(a, 2), RMove(a, 1));
    for (; !Mod(b, 2), RMove(b, 1));
    if (Same(a, b)) { // a == b 的高精版
      break;
    }
    if (a < b) {
      Swap(a, b); // 高精 swap(a, b)
    }
    Diff(a, b); // a -= b 高精版 
  }
  return LMove(a, min(cnt1, cnt2)); // LMove 为 a <<= b 的高精版
}

多个数的最大公约数:

显然答案一定是每个数的约数,那么也一定是每相邻两个数的约数。

代码:

ll GCD(ll a, ll b) {
  if (!b) {
    return a;
  }
  return GCD(b, a % b);
}

ll MGCD() {
  int _gcd = a[1];
  for (int i = 2; i <= n; ++ i) {
    _gcd = GCD(_gcd, a[i]);
  }
  return _gcd;
}

3rd 快速幂

我们知道 xa 乘上 xb,等于 xa+b,所以有 xaxb=xa+b。因此,设 f(a,b) 表示 ab 次方,我们可以这样算:

f(a,b)={if b=01if b=1aif bmod2=1a·f(x,b1)if bmod2=0f(a,b2)2

代码:

ll Qkpow(ll a, ll b) {
  if (!b) {
    return 1;
  }
  if (b == 1) {
    return a;
  }
  int tmp = Qkpow(a, (b >> 1)); // b >> 1 相当于 b / 2
  return tmp * tmp * ((b & 1)? a : 1); // b & 1 相当于 b % 2
} 

但是,我们还可以使用二进制的方式进行运算。其实跟前面差不多,但是省去了递归步骤。

代码:

// 无模数版

ll Qkpow(ll b, ll p) {
  ll ans = 1;
  for (; p; p >>= 1) {  // p >>= 1 相当于 p /= 2
    if (p & 1) {
      ans = ans * b;
    }
    b = b * b;
  }
  return ans;
}   

// 有模数版

ll Qkpow(ll b, ll p, ll mod) {
  ll ans = 1;
  for (; p; p >>= 1) {  // p >>= 1 相当于 p /= 2
    if (p & 1) {
      ans = ans * b % mod;
    }
    b = b * b % mod;
  }
  return ans;
}   
posted @   beautiful_chicken233  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示