數論板子

晨曦ccx發表於2024-04-14

線性篩求素數

int prime[MAXN]; // 儲存素數
bool is_not_prime[MAXN] = {1, 1}; // 0和1都不是素數

// 篩選 n 以內的所有素數
void xxs(int n) {
    for (int i = 2; i <= n; ++i) {
        if (!is_not_prime[i]) { // 如果i是素數
            prime[++prime[0]] = i;
        }
        for (int j = 1; j <= prime[0] && i * prime[j] <= n; ++j) {
            is_not_prime[i*prime[j]] = 1;
            // 如果i中包含了該質因子,則停止
            if (i % prime[j] == 0) break;
        }
    }
}

尤拉函式

求單個

int euler_phi(int n) {
  // 如果存在大於根號n的質因子,至多有一個
  int m = int(sqrt(n + 0.5));
  int ans = n;
  // 跟判定素數很像
  for (int i = 2; i <= m; i++) {
    // 如果i能整除n,則i是n的因子,也會質因子(看下面的while)
    if (n % i == 0) {
      ans = ans / i * (i - 1); // 根據定義計算
      // 根據唯一分解定理,去掉i的冪
      while (n % i == 0) n /= i;
    }
  }
  // 如果最後n>1,則為一個大於根號n的一個質因子
  if (n > 1) ans = ans / n * (n - 1);
  return ans;
}

求多個

// 整體框架為線性篩素數的程式碼,中間根據尤拉函式的性質加了幾條語句而已
int n, phi[N], prime[N], tot;
bool not_prime[N]; // true表示不是素數,false表示是素數
void getPhi() {
    int i, j, k;
    phi[1] = 1;
    for (i = 2; i <= n; ++i) {
        if (!not_prime[i]) {
            prime[++tot] = i;
            phi[i] = i-1; // 根據性質2
        }
        for (j = 1; j <= tot; ++j) {
            k = i * prime[j];
            if (k > n) break;
            not_prime[k] = true;
            if (i % prime[j] == 0) { // 變形1
                phi[k] = prime[j] * phi[i];
                break;
            } else { // 變形2
                phi[k] = (prime[j]-1) * phi[i];
            }
        }
    }
}

gcd求最大公約數

// 遞迴形式
int gcd(int x, int y) {
    return (y == 0 ? x : gcd(y, x%y));
}

// 非遞迴形式
int gcd(int x, int y) {
    int r = x % y; // 取餘數
    while (r) { // 餘數不為0,交換變數,繼續做除法
        x = y;
        y = r;
        r = x % y;
    }
    return y; // 餘數為0時,除數為gcd
}

乘法逆元

費馬小定理求逆元

typedef long long ll;
ll quickpow(ll a, ll n, ll p) { //快速冪求 a^n % p
    ll ans = 1;
    while(n) {
        if(n & 1) ans = ans * a % p;
        a = a * a % p;
        n >>= 1;
    }
    return ans;
}

ll niyuan(ll a, ll p) { //費馬小定理求逆元 a^(p-2)%p
    return quickpow(a, p - 2, p);
}

線性求1-n逆元

// 因為 1<i<p,所以 p/i 一定小於 p
ny[1] = 1;
for (int i = 2; i < p; ++i) {
    ny[i] = (long long)(p - p / i) * ny[p % i] % p; // 注意最後的模 p 不要忘記
}

中國剩餘定理

物不知數問題

LL CRT(int k, LL a[], LL r[]) {
  LL n = 1, ans = 0;
  for (int i = 1; i <= k; i++) n = n * r[i];
  for (int i = 1; i <= k; i++) {
    LL m = n / r[i], b, y;
    exgcd(m, r[i], b, y);  // b * m mod r[i] = 1
    ans = (ans + a[i] * m * b % n) % n;
  }
  return (ans % n + n) % n;
}

盧卡斯定理

求大組合數取模

// 需要先預處理出fact[],即階乘
ll C(ll n, ll m, ll p) {
    return n < m ? 0 : fact[n] * inv(fact[m], p) % p * inv(fact[n - m], p) % p;
}

ll Lucas(ll n, ll m, ll p) {
  if (m == 0) return 1;
  return (C(n % p, m % p, p) * Lucas(n / p, m / p, p)) % p;
}

詳解網址
數論基礎