天天看點

數論小結

記錄藍橋杯中常用的數論模闆

1-1歐幾裡得算法gcd

typedef long long ll;
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }
           

1-2 最小公倍數

int icm(ll a, ll b) { return a * b / gcd(a, b); }
           

1-3 擴充歐幾裡得exgcd

exgcd求解線性方程組模闆

int x, y;

//擴充歐幾裡得
int exgcd(int a, int b) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    int res = exgcd(b, a % b);
    int x1 = x;
    x = y, y = x1 - a / b * y;
    return res;
}

//求解線性方程 解為x和y
int line(int a, int b, int m) {
    int d = exgcd(a, b);
    if (m % d != 0) return -1;
    int n = m / d;
    x *= n, y *= n;
    return d;
}
           

exgcd一道例題藍橋杯決賽:一步之遙

從昏迷中醒來,小明發現自己被關在X星球的廢礦車裡。
礦車停在平直的廢棄的軌道上。
他的面前是兩個按鈕,分别寫着“F”和“B”。

小明突然記起來,這兩個按鈕可以控制礦車在軌道上前進和後退。
按F,會前進97米。按B會後退127米。
透過昏暗的燈光,小明看到自己前方1米遠正好有個監控探頭。
他必須設法使得礦車正好停在攝像頭的下方,才有機會争取同伴的援助。
或許,通過多次操作F和B可以辦到。

礦車上的動力已經不太足,黃色的警示燈在默默閃爍...
每次進行 F 或 B 操作都會消耗一定的能量。
小明飛快地計算,至少要多少次操作,才能把礦車準确地停在前方1米遠的地方。

請填寫為了達成目标,最少需要操作的次數。

注意,需要送出的是一個整數,不要填寫任何無關内容(比如:解釋說明等)

           

使用exgcd的做法,因為97,127互質。兩組特解之和即為答案

#include <bits/stdc++.h>
using namespace std;
int x, y;
int exgcd(int a, int b) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    int res = exgcd(b, a % b);
    int x1 = x;
    x = y, y = x1 - a / b * y;
    return res;
}
int line(int a, int b, int m) {
    int d = exgcd(a, b);
    if (m % d != 0) return -1;
    int n = m / d;
    x *= n, y *= n;
    return d;
}

int main() {
    int d, a = 97, b = -127;
    d = line(97, -127, 1);
    cout << d << endl;  //求解方程2x + 7y = 1的 未知數x和未知數y的一個解
    cout << x << " " << y << endl;
    cout << abs(x) + abs(y) << endl;
    b = 127 / d;  //求解第一個大于0的解 先把b對gcd(a,b)化簡
                  // cout<<"第一個大于0的解x:"<<(x%b+b)%b<<endl;
}
           

2-3:線性方程什麼時候有解,什麼時候無解,無解的最大值是多少

藍橋杯往屆例題:2014年A組-買不到的數目 (求系數為正整數時方程,無解時的最大上界:數學規律a*b-a-b)

藍橋杯往屆例題:2017年AB組-包子湊數(問什麼時候無解,當a1,a2,a3....an互質時無解)

3:同餘方程

3-1exgcd解同餘方程

将同餘方程轉換為 線性方程,當且僅當b是gcd(a,n)的倍數,n是餘數

數論小結

3-2:一道例題:poj1061青蛙的約會

寫出同餘方程,轉成線性方程,使用exgcd求解,求大于0的第一個解的公式:b = b/d,x = (x%b+b)%b;

數論小結

4-1:費馬小定理

數論小結

5-1:歐拉函數

//歐拉函數:求出小于等于n的  與n互質的個數,如果求多個數的歐拉值則要 篩法歐拉函數
using ll = long long;
ll Euler(ll n) {
    ll ans = n;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            ans = ans / i * (i - 1);
            while (n % i == 0) n /= i;
        }
    }
    if (n > 1) ans = ans / n * (n - 1);
    return ans;
}
           

6-1:快速幂

using ll = long long;
ll qpow(ll a, ll b, ll mod) {
    ll ans = 1;
    a %= mod;
    for (; b; a = a * a % mod, b >>= 1)
        if (b & 1) ans = ans * a % mod;
    return ans;
}
           

6-2:快速乘

using ll = long long;
ll qmul(ll a, ll b, ll mod) {
    ll ans = 0;
    for (; b; a = (a + a) % mod, b >>= 1)
        if (b & 1) ans = (ans + a) % mod;
    return ans;
}
           

7-1:素數篩

vector<bool> prime;
void Prime(int n) {
    prime.resize(n + 10, true);
    for (int i = 2; i * i <= n; ++i) {
        if (prime[i])
            for (int j = i * i; j <= n; j += i) prime[j] = false;
    }
}
           

8-1:日期計算-基姆拉爾森

// 根據日期判斷星期幾
int Day(int y, int m, int d) {
    if (m == 1 || m == 2) m += 12, y -= 1;
    return (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400 + 1) %
           7;
}
           

9-1:康托展開 & 逆康托展開

【原理】

\[X = A[0] * (n-1)! + A[1] * (n-2)! + … + A[n-1] * 0!\\

(A[i]表示在位置i後比位置i上數小的數的個數)

\]

【舉例】

\[在 (1, 2, 3, 4, 5) 5個數的排列組合中,計算 (3, 4, 1, 5, 2) 的康托展開值\\

X = 2 * 4! + 2 * 3! + 0 * 2! + 1 * 1! + 0 * 0! = 61

const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800};

int cantor(int *a) { //算出全排列對應的哈希值
    int x = 0;
    for (int i = 0; i < 9; i++) {
        int smaller = 0;
        for (int j = i + 1; j < 9; j++) {
            if (a[j] < a[i]) smaller++;
        }
        x += FAC[9 - i - 1] * smaller;
    }
    return x + 1;
    //注意全排列數組a是從零開始的
}

void decantor(int x, int *ans) { //x哈希值,n數字個數,a算出的全排列
    x--;
    vector<int> v;
    for (int i = 1; i <= 9; i++) v.push_back(i);
    for (int i = 0; i < 9; i++) {
        int r;
        r = x / FAC[9 - i - 1];
        x = x % FAC[9 - i - 1];
        sort(v.begin(), v.end());
        ans[i] = v[r];
        v.erase(v.begin() + r);
    }
    //注意算出的全排列數組ans是從0開始的
}