基础数论总结

质因数与唯一分解定理

问题一:求 \([1, n]\) 内约束个数最多的数的约数个数

此处 \(n\) 的范围是 \([1, 10^{18}]\),直接枚举求解肯定不行。
考虑任意一个大于 \(1\) 的数 \(n\) 数可以被质因数分解为:

\[n = \prod_{i = 1}^{k} p_i^{a_i}, 其中 \ p_i \ 为 \ n \ 的一个质因数. \]

所以它的约数个数就是 \(\prod_{i = 1}^{k} (a_i + 1)\)(即每个质因数的次数都可以选 \([0, a_i]\) 中的任何一个数)。
可以通过 \(dfs\) 爆搜的方法构造,使一个数在 \([1, n]\) 中且约数尽可能大。
下面给出代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn = 1e3 + 7;

// 预处理一些质数使它们作为质因数, 不用太多
vector<int> p;
void init() {
	for (int i = 2; i <= 50; ++i) {
		bool ok = 1;
		for (int j = 2; j * j <= i; ++j)
		    if (i % j == 0) {ok = 0; break;}
		if (ok) p.push_back(i);
	}
}
ll n, ans;
ll qpow(ll x, int y) {
	ll res = 1;
	while (y) {
		if (y & 1) res = res * x;
		y >>= 1, x *= x;
	}
	return res;
} 
// 参数分别代表的意义:
// 第 cur 个素数为底,目前数字是 num,num 的约数个数是 sum, 再搜下去的指数最多为 lim
void dfs(int cur, ll num, ll sum, int lim) {
	if (cur == p.size()) return ;
	ans = max(ans, sum);
	for (int i = lim; i >= 1; --i) {
		ll mul = qpow(p[cur], i);
		if (num > n / mul) continue;
		dfs(cur + 1, num * mul, sum * (i + 1), i);
	}
}
int main() {
	init();
	int T; scanf("%d", &T);
	while (T--) {
        ans = 0;
        scanf("%lld", &n);
        // 由于 2^62 ≈ 4e18, 所以次数最多是 62, 再多就超出范围了.
        dfs(0, 1, 1, 62);
        printf("%lld\n", ans);
	}
	return 0;
} 

习题:洛谷 P1463 [POI2001] [HAOI2007] 反素数

问题二:\(a\)\(b\) 约数的充要条件

先把 \(a\)\(b\) 质因数分解。
很显然若存在 \(a\) 分解出的一个质因数 \(p\),它的次数是 \(k\),如果 \(b\) 没有这个质因数,或者 \(p\)\(b\) 中的次数小于 \(a\),那么 \(a\) 就不是 \(b\) 的约数。
反之,若对于 \(a\) 分解出的任意一个质因数 \(p\),它也在 \(b\) 中且【 \(p\)\(b\) 中的次数 】大于等于【 \(p\)\(a\) 中的次数】,那么 \(a\) 就是 \(b\) 的约数。
习题:CF 1444A Division

问题三:质因数分解 \(n!\)

数据范围是:\(n \in [1, 10^6]\)
很显然,我们只需要对 \([1, n]\) 中的每一个数进行质因数分解,然后把每一个质因数的次数叠加。
复杂度看起来是 \(O(n \sqrt{n})\),但实际上内层循环质因数分解时,不会每一次都跑满 \(\sqrt{n}\),这只有在 \(n\) 是质数时会跑满。
所以我们只需要提前跑一边质数筛,筛出 \([1, n]\) 的所有质数,这样在内层循环时遇到质数直接给它的次数 \(+1\),然后 \(continue\) 掉就好。
那时间复杂度是多少呢?发现质因数分解时,合数 \(n\) 的质因子最小为 \(2\),所以每一次 \(n\) 的大小至少减少一半,所以时间复杂度是 \(O(nlogn)\)

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e6 + 7;

int n;
bool isp[maxn];
int pr[maxn], cnt;
int pwr[maxn];
void Euler() {
	memset(isp, 1, sizeof(isp));
	isp[0] = isp[1] = 0;
	for (int i = 2; i <= n; ++i) {
		if (isp[i]) pr[++cnt] = i;
		for (int j = 1; j <= cnt && pr[j] * i <= n; ++j) {
			isp[i * pr[j]] = 0;
			if (i % pr[j] == 0) break;
		}
	}
}
int main() {
	scanf("%d", &n);
	// 特判 n = 1 的情况 
	if (n == 1) {
		printf("2 0\n");
		return 0;
	}
	Euler();
	for (int i = 2; i <= n; ++i) {
		if (isp[i]) {++pwr[i]; continue;}  // 遇到质数, 次数直接 +1 
		int tmp = i;
		for (int j = 2; j * j <= tmp; ++j) {
			if (isp[tmp]) break;  // 如果 tmp 已经是质数了, 直接 break 
			if (tmp % j == 0) {
		    	while (tmp % j == 0 && tmp > 0) {
		    		++pwr[j], tmp /= j;
				}
			}	
		}
		if (tmp > 1) ++pwr[tmp];
	}
	for (int i = 1; i <= cnt; ++i)
	    if (pwr[pr[i]]) printf("%d %d\n", pr[i], pwr[pr[i]]);
	return 0;
}

问题四:求满足 \(lcm(i, j) = n\)\((i, j)\) 的对数 \((i \leq j)\)

很显然 \(i, j \leq n\)
先不考虑 \(i, j\) 的大小关系,先对它们进行质因数分解,\(lcm(i, j)\) 其实就是对它们分解出的质因数取并集,然后再对每个质因数的次数取 \(max\)
所以可以先质因数分解 \(n = p_1^{a_1}p_2^{a_2} \dots p_k^{a_k}\),再分类讨论:

  1. \(i\)\(p_{x}\) 这一质因数,且次数就是 \(a_x\),那么 \(j\)\(p_x\) 这一项次数取多少都无所谓,只要不大于 \(a_k\),有 \(a_x + 1\) 种取法;
  2. \(i\) 没有 \(p_x\) 这一质因数,或者这一项的次数小于 \(a_x\),那么 \(j\) 的这一项就只能取 \(p_x^{a_x}\),而 \(i\) 的这一项可以取 \([0, a_x - 1]\),有 \(a_x\) 种取法。

综上,每一位都有 \(2 a_x + 1\) 种取法,所以共有 \(ans = \prod_{i = 1}^k (2a_i + 1)\) 种取法。
但是其中并不考虑 \(i, j\) 的大小关系,所以除了【 \(i, j\)\(n\) 的所有质因数次数均取满(即 \(i = j = n\))】的情况外,每种情况还多算了一次。
所以最终答案应该是 \((ans - 1) \ / \ 2 + 1\)

LightOJ - 1236 Pairs Forming LCM

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn = 1e7 + 7;
const int mod  = 1e9 + 7;

bitset<maxn> notp;
ll pri[maxn], cnt;
void Euler() {
	notp[0] = notp[1] = 1;
	for (int i = 2; i <= 1e7; ++i) {
		if (!notp[i]) pri[++cnt] = i;
		for (int j = 1; j <= cnt; ++j) {
			if (i * pri[j] > 1e7) break;
			notp[i * pri[j]] = 1;
			if (i % pri[j] == 0) break;
		}
	}
}

ll solve(ll n) {
	ll ans = 1;
	for (int i = 1; i <= cnt; ++i) {
		if (pri[i] * pri[i] > n) break;
		int c = 0;
		while (n % pri[i] == 0)
			++c, n /= pri[i];
		ans *= (2 * c + 1);
	}
	if (n > 1) ans *= 3;
	return (ans - 1) / 2 + 1;
}
int main() {
	Euler();
    int T; ll n;
    scanf("%d", &T);
    for (int i = 1; i <= T; ++i) {
    	scanf("%lld", &n);
    	printf("Case %d: %lld\n", i, solve(n));
	}
	return 0;
}

扩展欧几里德算法

用扩欧求解的题一般只需要求解 \(x\),不管 \(y\)。但是往往要求求解出的 \(x\) 是满足条件的最小正整数,我们看看来怎么解决这个要求。

求解不定方程 \(ax + by = c\) 时(只考虑有解的情况,即:\(gcd(a, b)|c\) 的情况),用扩欧求解出 \(ax + by = gcd(a, b)\) 的一组特解 \((x_0, y_0)\),然后将 \(x_0\) 乘以 \(c / gcd(a, b)\)(如果 \(y\) 也要求出来的话,也乘以),得到 \(ax + by = c\) 的一组特解。

先考虑构造通解:

假设 \(ax + by = c\) 求出来的一组特解是 \((x_0, y_0)\)
现有两式:

\[ax + by = c \ \dots (1) \\ ax_0 + by_0 = c \ \dots (2) \]

\((1) - (2)\) 得:

\[a(x - x_0) + b(y - y_0) = 0 \]

\(x' = x - x_0, y' = y - y_0, d = gcd(a, b)\),则:

\[\frac{a}{d}x' + \frac{b}{d}y' = 0 \Rightarrow |\frac{a}{d}x'| = |\frac{b}{d}y'|. \]

由于 \(x'\)\(y'\) 均为整数,且 \(\frac{a}{d}\)\(\frac{b}{d}\) 已经不互含任何公因子,所以 \(\frac{a}{d} | y'\)\(\frac{b}{d} | x'\),即:

\[x = x_0 + \frac{b}{gcd(a, b)} \times k, \ y = y_0 + \frac{a}{gcd(a, b)} \times k \ (k \in \mathbb{Z}) \]

我们就构造出通解了。

是否存在另外的解:

那是否存在另外的解呢?即:是否存在 \(x'' \neq x_0 + \frac{b}{gcd(a, b)} \times k, y'' \neq y_0 + \frac{a}{gcd(a, b)} \times k\),也满足这个 \(ax + by = c\) 呢?
答案是不存在,用反证法证明。

假设存在 \(x'' \neq x_0 + \frac{b}{gcd(a, b)} \times k, y'' \neq y_0 + \frac{a}{gcd(a, b)} \times k\),满足 \(ax'' + by''= c\),那么就会有 \(a(x - x'') + b(y - y'') = 0\)
\(x''' = x_0 + \frac{b}{gcd(a, b)} \times k, y''' = y_0 + \frac{a}{gcd(a, b)} \times k\),就有 \(a(x - x''') + b(y - y''') = 0\)
再将两式相减得:\(a(x''' - x'') + b(y''' - y'') = 0\)
在解不定方程时,\(a, b \neq 0\),所以 \(x''' = x''\)\(y''' = y''\)
与假设相违,故不成立。

综上:

这样我们就证明了:

\[ax + by = c \ 的解有且仅有: \\ \ \\ x = x_0 + \frac{b}{gcd(a, b)} \times k, \ y = y_0 + \frac{a}{gcd(a, b)} \times k \ (k \in \mathbb{Z}) \\ \ \\ 其中 (x_0, y_0) 为用扩展欧几里德算法求出的一组特解。 \]

回归要求:

我们现在就可以用通解求解出 \(x\) 的最小正整数值。
\(x = x_0 + \frac{b}{gcd(a, b)} \times k\) 可得:\(x_{min} = x_0 \ mod \ \frac{b}{gcd(a, b)}\)
此时 \(x_{min}\) 可能还是个负数,所以要给它这样:

\[令 M = \frac{b}{gcd(a, b)}, \\ \ \\ x_{min} = (x_{min} \ \% \ M + M) \ \% \ M. \]

完成 !!!

习题:

洛谷 P1516 青蛙的约会
洛谷 P1082 [NOIP2012 提高组] 同余方程
洛谷 P1292 倒酒
洛谷 P2421 [NOI2002] 荒岛野人


乘法逆元

没什么好讲的,基本上在题中都是以辅助的形式出现,而且都很板子。
要求掌握:

  1. 求一个数的乘法逆元(用扩欧来算)
  2. \([1, n]\) 的乘法逆元(线性递推)
  3. 求给定 \(n\) 个数 \(a_1,a_2,a_3,\dots,a_n\) 的乘法逆元(求出 \(\prod_{i = 1}^na_i\) 的乘法逆元后依次反带回去)

注意: 根据定义,模数 \(M\)\(mod \ M\) 的意义下并无乘法逆元。因此在 3 中,若 \(n\) 个数中有 \(M\),那么求这 \(n\) 个数的积时要跳过 \(M\),才能保证 \(\prod_{i = 1}^{n}a_i\) 不等于 \(0\)(由于求积时可能会溢出,所以要 \(mod \ M\)),且 \(gcd(\prod_{i = 1}^{n}a_i, M) = 1\) 可以使用扩欧求逆元。


欧拉函数、欧拉定理、扩展欧拉定理

欧拉定理于扩展欧拉定理的证明在这篇博客
这里证明笔者做题时看到的一些欧拉函数的性质及一些有用的定理:

证明:\(n = \sum_{d|n} \varphi(d)\)

首先要证明 \(f(x) = \sum_{d|x} \varphi(x)\) 是个积性函数
设正整数 \(x, y\) 满足 \(gcd(x, y) = 1\),那么:

\[f(x) \times f(y) = (\sum_{i|x} \varphi(i)) \times (\sum_{j|y} \varphi(j)) = \sum_{i|x}\sum_{j|y}(\varphi(i) \times \varphi(j)) = \sum_{d|xy} \varphi(d) = f(xy) \]

\(p\) 为质数,又有:

\[f(p^k) = \sum_{i = 1}^{k} \varphi(p^i) = 1 + (p - 1) + (p^2 - p) + \dots + (p^k - p^{k - 1}) = p^k \]

又因为:\(n = \prod_{i = 1}^kp_i^{a_i}\),所以:

\[f(n) = \prod_{i = 1}^{k}f(p_i^{a_i}) = \prod_{i = 1}^kp_i^{a_i} = n \]

证毕。

证明:\(\sum_{i = 1}^{n} gcd(i, n) = \sum_{d | n}d \times \varphi(n/d)\)

现在开始推公式(\([ \ ]\)里的可以看成一个 \(bool\) 表达式,成立就是 \(1\),不成立就是 \(0\)):

\[\sum_{i = 1}^{n} gcd(i, n) \\ = \sum_{j = 1}^{n} \sum_{i = 1}^{n} j \times [gcd(i, n) = j] \\ = \sum_{j = 1}^{n} \sum_{i = 1}^{n} j \times [gcd(i/j, n/j) = 1] \\ = \sum_{j = 1}^{n}(j \times \sum_{i = 1}^{n} [gcd(i/j, n/j) = 1]) \]

这就意味着,满足 \(gcd(i, n) = j\)\(i\) 数量,实际上就是 \(\varphi(n/j)\) 的值,那么:

\[\sum_{i = 1}^{n} gcd(i, n) = \sum_{d|n} d \times \varphi(n / d) \]

证毕。
习题:
洛谷 P2303 [SDOI2012] Longge的问题
洛谷 P2398 GCD SUM

证明:若 \(gcd(a, n) = 1\),那么满足 \(a^x \equiv 1 \ (mod \ n)\) 的最小正整数 \(x\)\(\varphi(n)\) 的约数

用反证法证明。
假设满足条件的最小正整数 \(x\) 不是 \(\varphi(n)\) 的约数,那么 \(\varphi(n) = kx + r,(0 < r < x)\)
因为 \(a^{x} \equiv 1 \ (mod \ n)\),所以:\(a^{kx} \equiv 1 \ (mod \ n)\),
再根据欧拉定理可得:\(a^{\varphi(n)} \equiv a^{kx + r} \equiv a^r \ (mod \ n)\)
这与 \(x\) 最小相矛盾,故不成立。

习题:洛谷 P10496 The Luckiest Number

最后一个问题:求 \(a_1^{a_2^{a_3^{...a_n}}} \ mod \ m\) 的值

很显然可以用扩展欧拉定理化简为:

\[a_1^{(a_2^{a_3^{...a_n}}) \ mod \ \varphi(m) + \varphi(m)} \ mod \ m \]

再递归求解即可。
习题:
洛谷 P1405 苦恼的小明
洛谷 P4139 上帝与集合的正确用法

posted @ 2025-01-25 15:48  syzyc  阅读(64)  评论(0)    收藏  举报