基础数论总结
质因数与唯一分解定理
问题一:求 \([1, n]\) 内约束个数最多的数的约数个数
此处 \(n\) 的范围是 \([1, 10^{18}]\),直接枚举求解肯定不行。
考虑任意一个大于 \(1\) 的数 \(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}\),再分类讨论:
- 若 \(i\) 有 \(p_{x}\) 这一质因数,且次数就是 \(a_x\),那么 \(j\) 的 \(p_x\) 这一项次数取多少都无所谓,只要不大于 \(a_k\),有 \(a_x + 1\) 种取法;
- 若 \(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)\),
现有两式:
\((1) - (2)\) 得:
令 \(x' = x - x_0, y' = y - y_0, d = gcd(a, b)\),则:
由于 \(x'\) 和 \(y'\) 均为整数,且 \(\frac{a}{d}\) 与 \(\frac{b}{d}\) 已经不互含任何公因子,所以 \(\frac{a}{d} | y'\) 且 \(\frac{b}{d} | x'\),即:
我们就构造出通解了。
是否存在另外的解:
那是否存在另外的解呢?即:是否存在 \(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''\)。
与假设相违,故不成立。
综上:
这样我们就证明了:
回归要求:
我们现在就可以用通解求解出 \(x\) 的最小正整数值。
由 \(x = x_0 + \frac{b}{gcd(a, b)} \times k\) 可得:\(x_{min} = x_0 \ mod \ \frac{b}{gcd(a, b)}\),
此时 \(x_{min}\) 可能还是个负数,所以要给它这样:
完成 !!!
习题:
洛谷 P1516 青蛙的约会
洛谷 P1082 [NOIP2012 提高组] 同余方程
洛谷 P1292 倒酒
洛谷 P2421 [NOI2002] 荒岛野人
乘法逆元
没什么好讲的,基本上在题中都是以辅助的形式出现,而且都很板子。
要求掌握:
- 求一个数的乘法逆元(用扩欧来算)
- 求 \([1, n]\) 的乘法逆元(线性递推)
- 求给定 \(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\),那么:
设 \(p\) 为质数,又有:
又因为:\(n = \prod_{i = 1}^kp_i^{a_i}\),所以:
证毕。
证明:\(\sum_{i = 1}^{n} gcd(i, n) = \sum_{d | n}d \times \varphi(n/d)\)
现在开始推公式(\([ \ ]\)里的可以看成一个 \(bool\) 表达式,成立就是 \(1\),不成立就是 \(0\)):
这就意味着,满足 \(gcd(i, n) = j\) 的 \(i\) 数量,实际上就是 \(\varphi(n/j)\) 的值,那么:
证毕。
习题:
洛谷 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\) 的值
很显然可以用扩展欧拉定理化简为:
再递归求解即可。
习题:
洛谷 P1405 苦恼的小明
洛谷 P4139 上帝与集合的正确用法

浙公网安备 33010602011771号