數論

喔不

質數

我最喜歡隨機了

判斷質數

如何判斷一個數是不是質數?

就找看看他有沒有 1 與他自己以外的因數

\(O(N)\)?

注意到(這是真的能注意到)\(\sqrt N\)以上都不會是 N 的因數

\(O(\sqrt N\))

一個判斷質數的隨機演算法

但是 N 在確定值域的情況下此演算法可以變成確定性演算法(不會猜錯)

複雜度為 \(O(k \times log ^ 3(N))\)

其中 k 為隨機取基數的次數

範例程式碼

#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize("O3", "unroll-loops")
#pragma GCC target("avx2")
using ll = long long;

ll modPow(ll a, ll d, ll n) {
    ll ans = 1;
    a %= n;
    while (d > 0) {
        if (d & 1) ans = (ans * a) % n;
        a = (a * a) % n;
        d >>= 1;
    }
    return ans;
}

bool millerRabin(int n) {
    if (n < 2) return false;
    if (n == 2 || n == 3) return true;
    if (n % 2 == 0) return false;
    
    // 將 n - 1 分解為 2^r * d,r, d為正整數,且 d 為奇數
    int d = n - 1;
    int r = 0;
    while ((d & 1) == 0) {
        d >>= 1;
        r++;
    }
    
    int bases[] = {2, 7, 61};// 對於 n < 2 ^ 32,只需測試這三個 base 即可保證正確性(根據 deterministic Miller-Rabin)
    
    for (int a : bases) {
        if (a >= n) continue;
        
        ll x = modPow(a, d, n); //x = a ^ (2 ^ s * d), s = 0
        // 若存在某個 s 使得 a ^ (2 ^ s * d) ≡ -1 mod n,則 a 是 "strong liar",n 可能是質數
        if (x == 1 || x == n - 1) continue; 
        bool composite = true;
        for (int i = 0; i < r - 1; i++) {
            x = (x * x) % n;
            if (x == n - 1) {
                composite = false; 
                break;
            }
        }
        if (composite) return false;// 若所有 s 都無法使 a ^ (2 ^ s * d) ≡ -1 mod n,則 n 為合數
    }
    return true; //n is a Prime
}
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    while(cin >> n) {
        if(millerRabin(n))cout << "質數\n";
        else cout << "非質數\n";
    }
    return 0;
}

數質數

給定一個 N,求 2 ~ N 範圍內的質數數量

我可以對每一個數字做質數判斷

\(O(N \times \sqrt N)\)

有點慢

埃氏篩

做一個 bool 陣列,裡面都放 true

false 代表合數,true 代表質數

從 2 開始跑,首先我發現 2 是質數,所以我跑到\(2^2 = 4\)的位置,把後面所有 2 的倍數都設成 false

跑到 3,發現他是質數,所以我跑到\(3^2 = 9\)的位置,把後面所有 3 的倍數都設成 false

跑到 4,發現他是合數,跳過

以此類推

直到跑到大於\(\sqrt N\)的位置

複雜度 \(O(N \times log (logN))\)

線性篩

發現埃式篩會重複地把一些已經是合數的數字再修成合數

而線性篩讓每個合數只被自身最小的質因數篩選過一遍

一樣做一個 bool 陣列,裡面都放 true(質數)

同時開一個 vector,裝目前已知的質數

從 2 開始,他是質數,所以把他丟到 vector 裡

接下來對於遍歷這個 vector,把裡面的質數乘以目前跑到的數

如果太大就離開遍歷,否則把該數改成合數

並且如果目前此數為質數的倍數,則也要離開遍歷

複雜度 \(O(N)\)

打表題

模運算

取餘數

符號

\(a \equiv 3 \mod p \)

即為 a 除以 p 的餘數等於 3 除以 p 的餘數

加減乘

(a + b) % p == (a % p + b % p) % p

(a - b) % p == (a % p - b % p) % p

(a * b) % p == ((a % p) * (b % p)) % p

(a / b) % p != ((a % p) / (b % p)) % p

很容易找到反例

費馬小定理

首先如果 p 為質數,且 a 不是 p 的倍數

則 \(a^p \equiv a\mod p\)

左右同除 a 變成 \(a^{p - 1} \equiv 1\mod p\)

模逆元

對於 a 的模逆元 b,要滿足以下條件

\(a \times b \equiv 1 \mod p\)

而剛剛說了 \(a^{p - 1} \equiv 1\mod p\)

所以兩邊再同除 a 變成 \(a^{p - 2} \equiv a^{-1}\mod p\)

\(a^{-1} = b\)

所以\(a^{p - 2} \equiv b\mod p\)

​這時,對於任意一個整數 x,\(x \div a \mod p = x \times b \mod p\)

而 \(a^{p-2} \mod p\) 可以用快速冪計算

費馬小定理

p-1?

#include <bits/stdc++.h>
using namespace std;
int main()
{
    long long int n,a,b,c,p=1000000007,ans1,ans2;
    cin>>n;
    while(n--){
        ans1=1;
        ans2=1;
        cin>>a>>b>>c;
        p--; // why?
        for(;c;c>>=1,b=b*b%p)if(c&1)ans1=(ans1*b)%p;
        p++;
        for(;ans1;ans1>>=1,a=a*a%p)if(ans1&1)ans2=(ans2*a)%p;
        cout<<ans2<<'\n';
    }
    return 0;
}

數學

首先\(a^{b^c}\)將改成 \(a^X\)

根據費馬小定理,\(a^{p-1} \equiv 1 \mod p\)

而我們可以將\(X\)改成\(q \times (p-1) + r\)的形式

所以\(a^{b^c} = a^{q \times (p-1) + r}\)

\(= a^{q \times (p-1)} \times a^r\)

\(a^{q \times (p-1)} \times a^r \equiv a^r \mod p\)

而\(X \equiv r \mod p - 1\) 

所以 \(a^{b^c} \equiv a^{(b^c) \mod p-1} \mod p\)

模逆元

分塊

區間總和

給定一個陣列,求一個範圍內的數值總和

\(O(N)\)爆搜?太慢

\(O(log(N))\)線段樹?欸你太快了這是下學期的

切塊

我把陣列切成好幾個等大的分塊

每個分塊存取其區間的總和

如果查詢的區間範圍涵蓋至少一個分塊

就可以直接把該分塊的值加進答案

複雜度

\(O(N/k + k)\),其中k是分塊大小

然後你會算幾不等式

\((N/k + k)/2 \geq \sqrt N\)

所以當 k 為 \(\sqrt N\)時,單次查詢的複雜度最小,為\(O(\sqrt N\))

單點修改

修改值,然後改分塊存的值

對就這樣

區間修改

如果範圍涵蓋至少一個分塊的話

把被完全覆蓋的分塊更新

其餘的值就一個一個一個更新

題目

數論

By ck11300768鄭博軒