算术基本定理

一个整数可以被表示成若干质数的乘积。例如:48=24×3, 49=72, 50=2×52

算术基本定理:设 a>1,那么必有 a=p1α1p2α2psαs,其中 pi (1is) 是两两不相同的质数,αi (1is) 表示对应质数的幂次。

朴素的分解质因数的时间复杂度和枚举约数一样都是 O(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;
}

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

假定 a=p1α1p2α2psαs,有以下推论:

推论da 的约数的充要条件是 d=p1e1p2e2pses, 0eiαi, 1is,即 d 中每个质数的幂次都不超过 a

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

例如 12=22×3, 72=23×3212 的每个质因子上的幂次都对应地不超过 72 的,因此 12|72

推论:若 b=p1β1p2β2psβs,这里允许某些 αiβi 为零,那么 (a,b)=p1δ1p2δ2psδs, δi=min(αi,βi), 1is,以及 [a,b]=p1γ1p2γ2psγs, γi=max(αi,βi), 1is

比如,10=2×5, 16=24,那么 (10,16)=2min(1,4)×5min(1,0)=21×50=2,且 [10,16]=2max(1,4)×5max(1,0)=24×50=80

另外,这个性质也是 [a1,a2](a1,a2)=a1a2 的一种直接证明,以 1016 为例:(10,16)[10,16]=2min(1,4)×5min(1,0)×2max(1,4)×5max(1,0)=2min(1,4)+max(1,4)×5min(1,0)+max(1,0)=21+4×51+010×16=2×5×24=21+4×51+0

所以 [a1,a2](a1,a2)=a1a2 的本质其实是 min(α,β)+max(α,β)=α+β

推论:用除数函数 τ(a) 表示 a 的所有正约数的个数,则 τ(a)=(α1+1)(αs+1)=τ(p1α1)τ(psαs)

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

比如,a=27×38×59,则 a 的正约数个数等于 (7+1)(8+1)(9+1)=8×9×10=720

推论:用除数和函数 σ(a) 表示 a 的所有正约数的和,则 σ(a)=p1α1+11p11psαs+11ps1=σ(p1α1)σ(psαs)

比如,a=120=23×3×5 的因子分别是 1,2,3,4,5,6,8,10,12,15,20,24,30,40,60,120

用等比数列求和公式 p1α1+11p11=1+p1++p1α1 展开计算 σ(120)=241213213152151=(23+22+21+1)(31+1)(51+1),把括号展开,发现 σ(120)=(23+22+21+1)(31+1)(51+1)=120+24+40+8+60+12+20+4+30+6+10+2+15+3+5+1,等于之前的约数之和。

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

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

给定 m1 (m130000), m2 (m210000)n (n10000) 个正整数 Si (Si2×109)。设 xi 是最小的使得 m1m2 整除 Sixi 的整数(也有可能不存在),求 min(x1,,xn)

分析m1m2 很大,难以直接处理。考虑 m1=p1α1p2α2psαs,那么 m1m2=p1α1m2p2α2m2psαsm2。接下来只需要解决题意中的 xi 的求解。

m1m2 中的每个质因子的幂次都不超过 Sixi 时,m1m2|Sixi 成立。Sixi 中的质因子是否出现由 Si 决定,而质因子出现了几次主要由 xi 决定。若 Si=p1e1p2e2psesps+1es+1ps+res+r,其中 ps+1ps+r 表示与 m1 无关的质因子,那么 xi 应该使得对于所有 1js,满足 αjm2ejxi

所以从 m1 的每个质因子 pj 出发:如果这个 pj 不能整除 Si,则说明 Si 不包含这个质因子,进而说明找不到题设要求的 xi;如果这个 Si 能整除 pj,那么只要求出对应的 ej,就能算出第 j 个质因子,要求 xi 不小于 αjm2ej,再对所有这样的要求取最大值,就得到了 xi。最后对所有合法的 xi 取最小值就是答案。

参考代码
#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 分解质因数之后是 p1α1p2α2psαs,则它的正因数个数是 (α1+1)(α2+1)(αs+1)

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

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

时间复杂度为 O(nm),这里的 m 指的是 n 以内的质数个数(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;
}
posted @   RonChen  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示