1. 程式人生 > 遊戲資訊 >中古戰錘跑團(二版)中形形色色的職業(十六)- 占星師

中古戰錘跑團(二版)中形形色色的職業(十六)- 占星師

因數,也稱為約數(英語:Divisor)是一個常見的數學名詞,用於描述自然數 \(a\) 和自然數 \(b\) 之間存在的整除關係,即 \(b\)可以被\(a\) 整除。這裡我們稱 \(b\)\(a\) 的倍數,\(a\)\(b\) 的因數或因子。

試除法求約數

​ 試除法的思路很簡單和求質數一樣,求一個數\(n\)的所有約束,可以列舉從 \(1 \sim n\) 的所有數,把它記錄下來。這裡有一個優化,如果 \(d|n\) 那麼 \(\frac{n}{d} | n\) ,所以約束也是成對出現的,只要列舉 \(\frac{n} {d}\)\(d\) 之中較小的那個即可。只需要使 \(d <= \frac{n} {d}\)

即只需要列舉到 \(d <= \sqrt{n}\)

【問題描述】

給定 \(n\) 個正整數 \(a_i\),對於每個整數 \(a_i\),請你按照從小到大的順序輸出它的所有約數。

【輸入格式】

第一行包含整數 \(n\)

接下來 \(i\) 行,每行包含一個整數 \(a_i\)

【輸出格式】

輸出共 \(n\) 行,其中第 \(i\) 行輸出第 \(i\) 個整數 \(a_i\) 的所有約數。

【輸入樣例】

2

6

8

【輸出樣例】

1 2 3 6
1 2 4 8

#include<bits/stdc++.h>
using namespace std;

vector<int> div(int n) {
    vector<int> ans;
    
    for (int i = 1; i <= n / i; i ++) {
        if (n % i == 0) {
            ans.push_back(i);
            if (i != n / i) ans.push_back(n / i);  // 注意邊界情況,有可能出現 n/i == i的情況導致重複加入數值
        }
    }    
    sort(ans.begin(), ans.end());
    
    return ans;
}


int main()
{
    int n;
    cin >> n;
    while (n --) {
        int x; cin >> x;
        auto ans = div(x);
        for (auto t : ans) cout << t << ' ';
        cout << endl;
    }
    
    
    return 0;
}

約數的個數

自然數 N 的因數個數以 \(d(n)\) 表示。若 \(N\) 唯一分解為

\({\displaystyle N=p_{1}^{a_{1}}\times p_{2}^{a_{2}}\times p_{3}^{a_{3}}\times \cdots \times p_{n}^{a_{n}}=\prod _{i=1}^{n}p_{i}^{k_{i}}}\),

\({\displaystyle d(N)=(a_{1}+1)\times (a_{2}+1)\times (a_{3}+1)\times \cdots \times (a_{n}+1)=\prod _{i=1}^{n}\left(a_{i}+1\right)}\)

例如 \(2646=2\times 3^{3}\times 7^{2}\),則其正因數個數 \(d(2646)=(1+1)\times (3+1)\times (2+1)=24\)

【問題描述】

給定 \(n\) 個正整數 \(a_i\),請你輸出這些數的乘積的約數個數,答案對 \(10^9 + 7\) 取模。

【輸入格式】

第一行包含整數 \(n\)

接下來 \(i\) 行,每行包含一個整數 \(a_i\)

【輸出格式】

輸出一個整數,表示所給正整數的乘積的約數個數,答案對 \(10^9 + 7\) 取模。

【輸入樣例】

3

2

6

8

【輸出樣例】

12

#include<bits/stdc++.h>
using namespace std;

const int mod = 1e9 + 7;
typedef long long ll;
map<int, int> p;

int main()
{
    int n, x;
    cin >> n;
    while (n --) {
        cin >> x;
        for (int i = 2; i <= x / i; i ++) {
            if (x % i == 0) {
                while (x % i == 0) x /= i, p[i] ++;
            }
        }
        if (x > 1) p[x] ++;
    }
    
    ll ans = 1;
    for (auto t : p) ans = ans * (t.second + 1) % mod; // define的時候mod不是整型,是double不能取模,const int mod = 1e9 + 7比較保險。
    cout << ans;
    
    return 0;
}

給定任意一個數\(n\),求其約數的個數。上題相當於求 \(96\) 的約數的個數。

約數之和

自然數 \(N\) 的正因數和,以因數函式 \({\displaystyle \sigma (N)}\)表示。由質因數分解而得。

\(N\) 唯一分解為 \({\displaystyle N=p_{1}^{a_{1}}\times p_{2}^{a_{2}}\times p_{3}^{a_{3}}\times \cdots \times p_{n}^{a_{n}}=\prod _{i=1}^{n}p_{i}^{k_{i}}}\), 則 \({\displaystyle \sigma (N)=\prod _{i=1}^{n}\left(\sum _{j=0}^{a_{i}}p_{i}^{j}\right)}\)

再由等比級數求和公式可知,上式亦可寫成:

\({\displaystyle {\begin{aligned}\sigma (N)&={\frac {p_{1}^{a_{1}+1}-1}{p_{1}-1}}\times {\frac {p_{2}^{a_{2}+1}-1}{p_{2}-1}}\times \cdots \times {\frac {p_{n}^{a_{n}+1}-1}{p_{n}-1}}&\end{aligned}}}\)

例如\({\displaystyle 2646=2\times 3^{3}\times 7^{2}}\),則其正因數之和

\({\displaystyle {\begin{aligned}\sigma (2646)&=(1+2)\times (1+3+9+27)\times (1+7+49)\\&={\frac {2^{2}-1}{2-1}}\times {\frac {3^{4}-1}{3-1}}\times {\frac {7^{3}-1}{7-1}}\\&=3\times 40\times 57\\&=6840\end{aligned}}}\)

【問題描述】

給定 \(n\) 個正整數 \(a_i\),請你輸出這些數的乘積的約數之和,答案對 \(10^9 + 7\) 取模。

【輸入格式】

第一行包含整數 \(n\)

接下來 \(i\) 行,每行包含一個整數 \(a_i\)

【輸出格式】

輸出一個整數,表示所給正整數的乘積的約數之和,答案對 \(10^9 + 7\) 取模。

【輸入樣例】

3

2

6

8

【輸出樣例】

252

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mod = 1e9 + 7;

int main()
{
    int n;
    map<int, int> p;
    cin >> n;
    while (n --) {
        int x; cin >> x;
        for (int i = 2; i <= x / i; i ++) {
            if (x % i == 0) {
                while (x % i == 0) x /= i, p[i] ++;
            }
        }
        if (x > 1) p[x] ++;
    }
    
    ll ans = 1;
    for (auto t : p) {
        int a = t.first, b = t.second;
        ll k = 1;
        while (b --) k = (k * a + 1) % mod;
        ans = ans * k % mod;
    }
    
    cout << ans;
    
    return 0;
}

程式碼前半部分與之前幾乎一樣,後半部分很像秦九韶演算法,主要是用於計算等比數列和。

例如 \(1+3+3^2+3^3\) 可以寫成 \(3(3(3+1)+1)+1\)CPU計算加法比乘法快,可以算的快一點也可以採用快速冪計算等比數列前\(n\)項和

最大公約數

求最大公約數一般採用歐幾里得演算法,歐幾里得演算法的核心其實是\(gcd(a, b) = gcd(b, a\ mod\ b)\)下面進行證明

  1. \(a\ mod \ b\)進行變換

    \[\begin{align*} a\ mod\ b &= a - \left \lfloor \frac{a}{b} \right \rfloor \times b\\ &=a - c\times b \end{align*} \]
  2. 證明對於\(a\)\(b\)的任意公約數\(k\),都是\(b\)\(a\ mod\ b\)公約數
    \(b\)的公約數,同時也是\(a-c\times b\) 的公約數

  3. 證明對於\(b\)\(a\ mod\ b\)的任意公約數\(m\),都是\(a\)\(b\)的公約數

    即證明\(m\)\(a\)的公約數,\(m\)可以整除\(a\ mod\ b\),則\(m\)可以整除\(a-c\times b\)所以\(m\)可以整除\(a\)

綜上所述,集合\(cd(a, b)\)等於集合\(cd(b, a\ mod\ b)\),則\(gcd(a, b) = gcd(b, a\ mod\ b)\),該過程的實現如下

int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}

順便一提,最小公倍數的求法\(lcm(a, b) = \frac{a\times b} {gcd(a, b)}\)

int lcm(a, b) {
	return a / gcd(a, b) * b; // 不要寫成a*b/gcd(a, b)可能會溢位,先除會讓數小一些
}