数論の基礎と応用

因数に関する考察

  • 1からnまでのすべての数の因数の総数はO(n log n)である。
  • 1からnまでの素数の個数はO(n / log n)である。
  • 伯トラン・チェビシェフの定理:n ≥ 1のとき、nと2nの間に少なくとも1つの素数がある。
  • 直角三角形の辺の長さの一般式:a = w * 2uv, b = w * (u^2 - v^2), c = w * (u^2 + v^2)、ここでu, v, wは正の整数。

問題

  • Common Divisors: a_1, a_2, ..., a_nの中で少なくとも2つがxの倍数となるxを見つける。
  • Fair Warning: gcd(a_i + y, a_2 + y, ..., a_n + y) = g_yとすると、g_y | (a_i - a_j)、1 ≤ i, j ≤ nである。

最大公約数に関する考察

GCD

  • gcd(a, b) = gcd(a, a + b) = gcd(a, b - a) = gcd(a, b mod a) = gcd(b, a mod b)
  • gcd(ab, ac) = a * gcd(b, c)
  • gcd(a, b) * lcm(a, b) = a * b

実装


template<typename T>
T gcd(T a, T b) {
    return (b == 0) ? a : gcd(b, a % b);
}

拡張ユークリッドのアルゴリズム (exGCD)

解法

方程式 ax + by = gcd(a, b)について、exGCDを用いて解を求める。

実装


template<typename T>
void exgcd(T a, T b, T &x, T &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return;
    }
    exgcd(b, a % b, y, x);
    y -= a / b * x;
}

同余に関する考察

同余類と縮同余類

  • mod m の同余類: Z_m = {0̅, 1̅, ..., (m-1)̅}
  • mod m の縮同余類: Z_m* = {x̅ | 0 ≤ x < m, gcd(x, m) = 1}

乗法的逆元

  • a ∈ Z_m* (gcd(a, m) = 1) に対して、b ∈ Z_m* が存在し、a * b ≡ 1 (mod m)

計算方法

  • フェルマーの小定理**: a^(p-1) ≡ 1 (mod p)、ここでpは素数で、pとaは互いに素。
  • 拡張ユーオッドのアルゴリズム**: 同余方程式 a * x ≡ 1 (mod p) を解く。
  • 階乗を利用した方法**: O(n + log V) で1からnまでの数の逆元を求める。
  • 線形再帰による方法**: O(n) で1からnまでの数の逆元を求める。

積性関数に関する考察

線形篩による計算

積性関数 f(x) に対して、f(1)の値を知り、質数pに対するf(p)の計算方法と、p^kに対するf(p^k)の計算方法を知ることで、線形篩を使用してf(x)の値を求める。

コード例


const int MAX_N = 1000000;
long long d, f[MAX_N], val[MAX_N];
bitset<MAX_N> book;
vector<pair<int, long long>> pr;

void euler(int n) {
    book[0] = book[1] = 1;
    f[0] = 0, f[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!book[i]) {
            pr.emplace_back(i, pow(i, d));
            f[i] = pow(i, d) + 1;
            val[i] = i;
        }
        for (auto v : pr) {
            if (1LL * i * v.first > n) break;
            book[i * v.first] = 1;
            if (i % v.first == 0) {
                val[i * v.first] = val[i] * v.first;
                if (i == val[i])
                    f[i * v.first] = f[i] * v.second + 1;
                else
                    f[i * v.first] = f[i / val[i]] * f[val[i] * v.first];
                break;
            } else {
                val[i * v.first] = v.first;
                f[i * v.first] = f[i] * f[v.first];
            }
        }
    }
}

タグ: 数論 最大公約数 同余 積性関数 線形篩

6月22日 20:48 投稿