算术基本定理

一个整数可以被表示成若干质数的乘积。例如:\(48=2^4 \times 3, \ 49 = 7^2, \ 50 = 2 \times 5^2\)

算术基本定理:设 \(a>1\),那么必有 \(a = p_1^{\alpha_1} p_2^{\alpha_2} \cdots p_s^{\alpha_s}\),其中 \(p_i \ (1 \le i \le s)\) 是两两不相同的质数,\(\alpha_i \ (1 \le i \le s)\) 表示对应质数的幂次。

朴素的分解质因数的时间复杂度和枚举约数一样都是 \(O(\sqrt{n})\)

int decomposition(int x) {
    // 分解x,数组a记录所有质数,返回分解出来的质数数量
    int cnt = 0;
    for (int i = 2; i <= x / i; i++) {
        while (x % i == 0) {
            a[++cnt] = i;
            x /= i;
        }
    }
    if (x > 1) a[++cnt] = x;
    return cnt;
}

选择题:有一个等比数列,共有奇数项,其中第一项和最后一项分别是 2 和 118098,中间一项是 486,请问以下哪个数是可能的公比?

  • A. 5
  • B. 3
  • C. 4
  • D. 2
答案

B

设等比数列的首项为 \(a_1\),公比为 \(q\),项数为 \(n\)。则第 \(i\) 项为 \(a_i = a_1 q^{i-1}\)。对于一个有奇数项的等比数列,中间项的平方等于首项与末项的乘积。即 \(a_{k+1}^2 = a_1 a_{2k+1}\)。题面的数据与这个性质是符合的。

而中间项 \(a_{k+1} = a_1 q^k\),将已知数值代入,可以得到 \(q^k = 243\)

现在需要找到一个公比 \(q\),它的某个整数 \(k\) 次方等于 243。可以对 243 进行质因数分解,\(243 = 3^5\)


算术基本定理是处理整除性和数论函数的有力工具。

假定 \(a = p_1^{\alpha_1} p_2^{\alpha_2} \cdots p_s^{\alpha_s}\),有以下推论:

推论\(d\)\(a\) 的约数的充要条件是 \(d = p_1^{e_1} p_2^{e_2} \cdots p_s^{e_s}, \ 0 \le e_i \le \alpha_i, \ 1 \le i \le s\),即 \(d\) 中每个质数的幂次都不超过 \(a\)

每个质因子上的幂次直接决定了两数之间的整除性。

例如 \(12 = 2^2 \times 3, \ 72 = 2^3 \times 3^2\)\(12\) 的每个质因子上的幂次都对应地不超过 \(72\) 的,因此 \(12 | 72\)

推论:若 \(b = p_1^{\beta_1} p_2^{\beta_2} \cdots p_s^{\beta_s}\),这里允许某些 \(\alpha_i\)\(\beta_i\) 为零,那么 \((a,b) = p_1^{\delta_1} p_2^{\delta_2} \cdots p_s^{\delta_s}, \ \delta_i = \min (\alpha_i, \beta_i), \ 1 \le i \le s\),以及 \([a,b] = p_1^{\gamma_1} p_2^{\gamma_2} \cdots p_s^{\gamma_s}, \ \gamma_i = \max(\alpha_i, \beta_i), \ 1 \le i \le s\)

比如,\(10 = 2 \times 5, \ 16 = 2^4\),那么 \((10,16) = 2^{\min(1,4)} \times 5^{\min(1,0)} = 2^1 \times 5^0 = 2\),且 \([10,16] = 2^{\max(1,4)} \times 5^{\max(1,0)} = 2^4 \times 5^0 = 80\)

另外,这个性质也是 \([a_1,a_2](a_1,a_2) = a_1a_2\) 的一种直接证明,以 \(10\)\(16\) 为例:\((10,16)[10,16] = 2^{\min(1,4)} \times 5^{\min(1,0)} \times 2^{\max(1,4)} \times 5^{\max(1,0)} = 2^{\min(1,4) + \max(1,4)} \times 5^{\min(1,0) + \max(1,0)} = 2^{1+4} \times 5^{1+0}\)\(10 \times 16 = 2 \times 5 \times 2^4 = 2^{1+4} \times 5^{1+0}\)

所以 \([a_1,a_2](a_1,a_2) = a_1a_2\) 的本质其实是 \(\min(\alpha,\beta) + \max(\alpha,\beta) = \alpha + \beta\)

推论:用除数函数 \(\tau(a)\) 表示 \(a\) 的所有正约数的个数,则 \(\tau(a) = (\alpha_1+1) \cdots (\alpha_s + 1) = \tau(p_1^{\alpha_1}) \cdots \tau(p_s^{\alpha_s})\)

相当于对于每个质因子上的幂次,可以取 \(0\)\(\alpha_i\) 中的任意整数,共 \(\alpha_i + 1\) 个,由乘法原理可以直接得出,约数的个数。

比如,\(a = 2^7 \times 3^8 \times 5^9\),则 \(a\) 的正约数个数等于 \((7+1)(8+1)(9+1) = 8 \times 9 \times 10 = 720\)

推论:用除数和函数 \(\sigma(a)\) 表示 \(a\) 的所有正约数的和,则 \(\sigma(a) = \dfrac{p_1^{\alpha_1 + 1} - 1}{p_1 - 1} \cdots \dfrac{p_s^{\alpha_s + 1} - 1}{p_s - 1} = \sigma(p_1^{\alpha_1}) \cdots \sigma(p_s^{\alpha_s})\)

比如,\(a = 120 = 2^3 \times 3 \times 5\) 的因子分别是 \(1,2,3,4,5,6,8,10,12,15,20,24,30,40,60,120\)

用等比数列求和公式 \(\dfrac{p_1^{\alpha_1 + 1} - 1}{p_1 - 1} = 1 + p_1 + \cdots + p_1^{\alpha_1}\) 展开计算 \(\sigma(120) = \dfrac{2^4 - 1}{2 - 1} \dfrac{3^2-1}{3-1} \dfrac{5^2-1}{5-1} = (2^3+2^2+2^1+1)(3^1+1)(5^1+1)\),把括号展开,发现 \(\sigma(120) = (2^3+2^2+2^1+1)(3^1+1)(5^1+1) = 120+24+40+8+60+12+20+4+30+6+10+2+15+3+5+1\),等于之前的约数之和。

公式是乘法原理在乘法分配律上的体现,也展现了质因子之间的独立性。

例题:P1069 [NOIP2009 普及组] 细胞分裂

给定 \(m_1 \ (m_1 \le 30000), \ m_2 \ (m_2 \le 10000)\)\(n \ (n \le 10000)\) 个正整数 \(S_i \ (S_i \le 2 \times 10^9)\)。设 \(x_i\) 是最小的使得 \(m_1^{m_2}\) 整除 \(S_i^{x_i}\) 的整数(也有可能不存在),求 \(\min(x_1, \cdots, x_n)\)

分析\(m_1^{m_2}\) 很大,难以直接处理。考虑 \(m_1 = p_1^{\alpha_1} p_2^{\alpha_2} \cdots p_s^{\alpha_s}\),那么 \(m_1^{m_2} = p_1^{\alpha_1 m_2} p_2^{\alpha_2 m_2} \cdots p_s^{\alpha_s m_2}\)。接下来只需要解决题意中的 \(x_i\) 的求解。

\(m_1^{m_2}\) 中的每个质因子的幂次都不超过 \(S_i^{x_i}\) 时,\(m_1^{m_2} | S_i^{x_i}\) 成立。\(S_i^{x_i}\) 中的质因子是否出现由 \(S_i\) 决定,而质因子出现了几次主要由 \(x_i\) 决定。若 \(S_i = p_1^{e_1} p_2^{e_2} \cdots p_s^{e_s} p_{s+1}^{e_{s+1}} \cdots p_{s+r}^{e_{s+r}}\),其中 \(p_{s+1}\)\(p_{s+r}\) 表示与 \(m_1\) 无关的质因子,那么 \(x_i\) 应该使得对于所有 \(1 \le j \le s\),满足 \(\alpha_j m_2 \le e_j x_i\)

所以从 \(m_1\) 的每个质因子 \(p_j\) 出发:如果这个 \(p_j\) 不能整除 \(S_i\),则说明 \(S_i\) 不包含这个质因子,进而说明找不到题设要求的 \(x_i\);如果这个 \(S_i\) 能整除 \(p_j\),那么只要求出对应的 \(e_j\),就能算出第 \(j\) 个质因子,要求 \(x_i\) 不小于 \(\left\lceil \dfrac{\alpha_j m_2}{e_j} \right\rceil\),再对所有这样的要求取最大值,就得到了 \(x_i\)。最后对所有合法的 \(x_i\) 取最小值就是答案。

参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 200;
int cnt[N], cnts[N];
int main()
{
    int n, m1, m2; 
    scanf("%d%d%d", &n, &m1, &m2);
    int m = m1;
    // 对m1分解质因数
    for (int i = 2; i * i <= m; i++) {
        while (m1 % i == 0) {
            cnt[i]++;
            m1 /= i;
        }
    }
    int ans = -1;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < N; j++) cnts[j] = 0;
        int s;
        scanf("%d", &s);
        // 对s分解质因数
        for (int j = 2; j * j <= m; j++) {
            while (s % j == 0) {
                cnts[j]++;
                s /= j;
            }
        }
        bool ok = true;
        int tmp = 0;
        if (m1 > 1) {
            int cntm = 0;
            while (s % m1 == 0) {
                cntm++; s /= m1;
            }
            if (cntm == 0) continue;
            tmp = (m2 + cntm - 1) / cntm;
        }
        for (int j = 2; j * j <= m; j++) {
            if (cnt[j] != 0 && cnts[j] == 0) {
                ok = false; break;
            } 
            if (cnt[j] > 0 && cnts[j] > 0) {
                tmp = max(tmp, (cnt[j] * m2 + cnts[j] - 1) / cnts[j]);
            }
        }
        if (ok && (ans == -1 || tmp < ans)) ans = tmp;
    }
    printf("%d\n", ans);
    return 0;
}

习题:P9836 种树

解题思路

假设树高 \(p\) 分解质因数之后是 \(p_1^{\alpha_1} p_2^{\alpha_2} \cdots p_s^{\alpha_s}\),则它的正因数个数是 \((\alpha_1 + 1)(\alpha_2 + 1) \cdots (\alpha_s + 1)\)

每次施肥实际上是考虑肥料 \(w\) 的每一个质因子该用在哪棵树上,这就相当于把该树高度的这个质因子的指数 \(+1\),假设原来的指数是 \(x\),则施肥后指数变为 \(x+1\),而指数是 \(x\) 时对正因子个数的贡献是 \(x+1\),指数是 \(x+1\) 时对正因子个数的贡献是 \(x+2\),因此整体的正因子个数乘积变为原来的 \(\dfrac{x+2}{x+1}\)

分析这个式子可以发现,原来的 \(x\) 越小,对其施肥后对整体的贡献增长最大,所以应该把这个质因子用在该质因子指数最小的那棵树上,按照这个策略把肥料中的每个质因子使用出去即可。

时间复杂度为 \(O(nm)\),这里的 \(m\) 指的是 \(n\) 以内的质数个数(\(\le 1500\))。

参考代码
#include <cstdio>
const int N = 10005;
const int M = 1500;
const int MOD = 998244353;
int cnt[N][M], prime[N], len;
bool is_prime[N];
void init() {
    for (int i = 2; i < N; i++) is_prime[i] = true;
    for (int i = 2; i < N; i++) {
        if (is_prime[i]) {
            prime[++len] = i;
            for (int j = i * 2; j < N; j += i) is_prime[j] = false;
        }
    }
}
int main()
{
    init();
    int n, w;
    scanf("%d%d", &n, &w);
    for (int i = 1; i <= n; i++) {
        int x;
        scanf("%d", &x);
        for (int j = 1; j <= len; j++) {
            cnt[i][j] = 1;
            while (x % prime[j] == 0) {
                cnt[i][j]++;
                x /= prime[j];
            }
        }
    }
    for (int i = 1; i <= len; i++) {
        while (w % prime[i] == 0) {
            int minj = 1; 
            for (int j = 2; j <= n; j++)
                if (cnt[j][i] < cnt[minj][i]) minj = j;
            cnt[minj][i]++;
            w /= prime[i];
        }
    }
    int ans = 1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= len; j++)
            ans = 1ll * ans * cnt[i][j] % MOD;
    printf("%d\n", ans);
    return 0;
}

习题:P8980 「DROI」Round 1 游戏

解题思路

根据算数基本定理,任何正整数 \(x\) 都可以唯一分解为质因数的乘积 \(x = p_1^{\alpha_1}p_2^{\alpha_2}\cdots p_k^{\alpha_k}\)

要唯一确定 \(x\)(在 \(1\)\(n\) 范围内),等价于要唯一确定 \(x\) 在每个质数 \(p \le n\) 下的指数 \(\alpha\)

对于每一次询问和回答,根据 GCD 的性质,回答等价于告诉对方 \(v_p(g_i) = \min(v_p(x),v_p(a_i))\),其中 \(v_p(n)\) 表示 \(n\) 中质因子 \(p\) 的指数。

考虑单个质数 \(p\),设 \(k_{\max}\) 为满足 \(p^k \le n\) 的最大整数(即 \(k_{\max} = \lfloor \log_p n \rfloor\)),那么 \(x\)\(p\) 的指数 \(v_p(x)\) 的可能取值范围是 \([0,k_{\max}]\)

如果某次询问的 \(a_i\) 满足 \(v_p(a_i) = k_{\max}\),那么 \(\min(v_p(x),v_p(a_i))\) 的结果必然等于 \(v_p(x)\)(因为 \(v_p(x) \le k_{\max} = v_p(a_i)\)),这意味着这次询问直接暴露了 \(x\)\(p\) 的确切指数

反过来讲,如果某次询问的 \(a_i\) 中没有满足这样条件的质因子,那么这次询问获取不到什么确定性信息。

而如果对于所有的询问 \(a_i\),都不包含某个 \(p\)\(k_{\max}\),那么肯定可以构造出一个值使对方无法确定(只要将 \(x\) 中那个 \(p\) 的指数设为 \(k_{\max}\) 即可)。

因此游戏结束的条件就是对于所有的 \(p \le n\),都至少存在一次询问 \(a_i\) 使得该询问“覆盖”了 \(p\) 的最大可能指数。而游戏持续的最长轮数,就是所有质数被“确定”所需的最早时间的最大值。如果有任何一个质数永远无法被确定,则游戏无限进行,输出 game won't stop

实际上在每次有效询问中,最多只能确定一个质数在 \(x\) 中的次数,这是因为 \(a_i \le n\),不可能有这样一个数使得其包含两个质数的“最大指数”。所以在 \(Q\) 次询问中最多确定 \(Q\) 个质数的次数,也就是说 \(n\) 的范围内的质数如果大于等于 \(Q_{\max} = 2\times 10^6 + 1\) 个,那么一定不可能让游戏结束。因此,在预处理质数筛时处理到大约 \(3.25 \times 10^7\) 即足够。

参考代码
#include <cstdio>
#include <vector>
#include <algorithm>
using ll = long long;
using std::vector;
using std::max;

const int Q = 2000005;
// B: 筛法的上限阈值。如果 n >= B,则认为质数太多无法覆盖,直接判定无法停止。
// 这个阈值大约是 3.25 * 10^7,在此范围内质数个数接近题目给定的询问次数上限。
const int B = 32500000;

bool flag[B]; // 标记数组,true表示合数
ll a[Q];      // 存储询问序列
int minp[B];  // 存储每个数的最小质因子在 p 数组中的下标
int pos[Q];   // 记录每个质数被“确定”的最早时间(询问编号)
vector<int> p; // 存储所有质数

// 线性筛预处理
void init() {
    for (int i = 2; i < B; i++) {
        if (!flag[i]) {
            // 如果 i 是质数,记录其在 p 中的下标
            minp[i] = p.size(); 
            p.push_back(i); 
        }
        for (int j = 0; j < p.size(); j++) {
            int x = p[j];
            if (1ll * x * i >= B) break;
            flag[i * x] = true; 
            minp[i * x] = j; // 记录 i*x 的最小质因子下标
            if (i % x == 0) break;
        }
    }
}

void solve() {
    ll n;
    int q; 
    scanf("%lld%d", &n, &q);
    for (int i = 1; i <= q; i++) {
        scanf("%lld", &a[i]);
    }

    // 如果 n 超过了筛的范围,说明 n 很大,质数非常多,
    // 在给定的询问次数下无法区分所有质数的指数,因此游戏不会停止。
    if (n >= B) {
        printf("game won't stop\n");
        return;
    }

    // 初始化:清空小于等于 n 的所有质数的 pos 记录
    // pos[i] 表示第 i 个质数满足区分条件的最早询问轮数,0表示未满足
    for (int i = 0; i < p.size(); i++) {
        if (p[i] > n) break;
        pos[i] = 0;
    }

    // 处理每个询问
    for (int i = 1; i <= q; i++) {
        // 分解 a[i] 的质因数
        // 由于 a[i] <= n < B,可以直接利用 minp 数组快速分解
        while (a[i] > 1) {
            int j = minp[a[i]]; // 获取当前最小质因子的下标
            int num = 1;        // num 用于计算当前质因子 p[j] 在 a[i] 中的最高次幂
            // 除去所有的 p[j] 因子
            while (a[i] % p[j] == 0) {
                num *= p[j]; 
                a[i] /= p[j];
            }
            // 判断关键条件:
            // 如果 num * p[j] > n,说明 x 中 p[j] 的指数已经是最大值。
            // 此时 gcd(x, a[i]) 可以精确确定 x 中 p[j] 的指数。
            if (1ll * num * p[j] > n && pos[j] == 0) {
                pos[j] = i; 
            }
        } 
    } 

    int ans = 0;
    // 检查所有小于等于 n 的质数是否都被“确定”了
    for (int i = 0; i < p.size(); i++) {
        if (p[i] > n) break;
        // 如果存在某个质数没有被任何询问区分出来,则无法唯一确定 x
        if (pos[i] == 0) {
            printf("game won't stop\n"); 
            return;
        }
        // 游戏结束的时间取决于最后一个被确定的质数的时间
        ans = max(ans, pos[i]);
    }
    printf("%d\n", ans);
}

int main()
{
    init(); // 预处理质数
    int t; 
    scanf("%d", &t);
    for (int i = 1; i <= t; i++) solve();
    return 0;
}
posted @ 2024-09-30 14:38  RonChen  阅读(165)  评论(0)    收藏  举报