初等数论
我是鸽子咕咕咕...
质数
质数(prime number)又称素数,有无限个。质数定义为在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数,这样的数称为质数(prime)
除了 1 和本身还有其他约数的数是合数(compound)。如果 n 是合数,则 n 必有一个小于或等于 \(n^{\frac{1}{2}}\) 的素因子。
注意:1 既不是质数也不是合数;2 是最小的质数,且是唯一的偶质数。质数的个数是无限的。
算数基本定理(唯一分解定理)
每个正整数都可以唯一地表示成质数的乘积。即有唯一的分解方案:
\(x=p1^{a1}p2^{a2}...pn^{an}\)
其中 \(a_i\) 是正整数,\(p_i\) 是质数,且满足 \(p1<p2<...pn\)
则 x 的约数个数 = \((a1+1)(a2+1)...(an+1)\)
质数的判定
1.基本筛法
对正整数 n,如果用 2 到 \(n^{\frac{1}{2}}\) 之间的所有整数去除,均无法整除,则 n 为质数。时间复杂度为 O(sqrt(n))。
2.Eratothenes 筛选法
即埃氏筛法。基本思想:质数的倍数一定不是质数。
在筛选的过程中我们发现,2 和 3 都会把 6 标记为合数,实际上小于 \(x^2\) 的 x 的倍数在扫描更小的数时已经被标记过了,因此可以对埃氏筛法进行优化,对于每个数 x 把大于等于 \(x^2\) 的 x 的倍数标记为合数即可。
Code
void prim(int n)
{
memset(vis, 0, sizeof(vis));
for(int i = 2; i <= n; i++)
{
if(vis[i] == 1) continue;
cout << i << " ";
for(int j = i; j <= n / i; j++) vis[i * j] = 1;
}
}
3.欧拉筛
来填坑~
用质数筛合数。废话少说上代码:
代码
void PRIME(int n)
{
memset(noline, false, sizeof(noline));
noline[1] = true;
for(int i = 2; i <= n; i++)
{
if(!noline[i]) prime[++cnt] = i;
for(int j = 1;j <= cnt; j++)
{
if(prime[j] * i > n) break;
noline[prime[j] * i] = true;
if(i % prime[j] == 0) break;
}
}
}
之所以除去 \(i \% prim_j\) 的情况,是因为从现在开始筛下去的所有情况在 \(\frac{i}{prim_j} * prim_j\) 时已经被筛过了。
质因数分解
数论类题目中,经常需要分解质因数。质因数分解问题是指:给出一个正整数,将其写成几个质数的乘积。根据唯一分解定理,这样的分解结果应该是独一无二的。
结论:一个数 n 至多有一个大于 sqrt(n) 的质因子
证明:假设 a,b 是由 n 分解出的两个质因子,且 a,b>sqrt(n),那么 a * b 是 n 的因子,然而 a * b > n,所以 a * b 不可能是 n 的因子,所以原命题成立。
这样最终就得到了质因数分解的结果,易知其时间复杂度为 O(sqrt(n))。
特别地,若 n 没有任何 2~sqrt(n) 的数这整除,则 n 是质数,无需分解。
Code
void divide(int n)
{
m = 0;
for(int i = 2; i * i <= n; i++)
if(n % i == 0) // i是质数
{
p[++m] = i, c[m] = 0;
while(n % i == 0) n /= i, c[m]++; // 除掉所有的i
}
if(n > 1) p[++m] = n, c[m] = 1;
for(int i = 1; i <= m; i++) cout << p[i] << "^" << c[i] << endl;
}
例题
1.NOIP 2012 质因数分解
因为 n 能被两个质数整除,即 \(n=p^{1}q^{1}\) ,因此 n 的因数个数为 \((1+1)(1+1) = 4.\)
已知 n 的一组因数是 1、n,而剩下的那一组因数就是 p、q.因此我们从 2 到 sqrt(n) 枚举 n 的因数,碰到第一个就输出 \(\frac{n}{i}\)(因为要求输出大的那个)
Code
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
long long n;
void solve()
{
for(long long i = 2; i * i <= n; i++)
if(n % i == 0)
{
cout << n / i << endl;
return ;
}
}
int main()
{
cin >> n;
solve();
return 0;
}
2.CF776B Sherlock and his girlfriend
首先观察到,任意质数都没有质因子,而所有合数都至少有一个质因子。显然存在一种合法的方案:所有质数染色为 1 ,所有合数染色为 2,最少的颜色数就是 2
注意,当 [2,n] 不存在合数(即 n≤3),最少颜色数为 1.
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2333333;
int n, vis[N];
void solve()
{
memset(vis, 0, sizeof(vis));
for(int i = 2; i <= n; i++)
{
if(vis[i]) continue;
for(int j = 2; j * i <= n; j++)
vis[i * j] = 1;
}
}
int main()
{
cin >> n;
n++;
if(n <= 3)
{
cout << "1\n";
for(int i = 2; i <= n; i++) cout << "1 ";
return 0;
}
solve();
printf("2\n");
for(int i = 2; i <= n; i++)
{
if(vis[i]) cout << "2 ";
else cout << "1 ";
}
return 0;
}
整除
设 a,b 是两个整数,且 \(b \neq 0\),如果存在整数 k,使 a = bk,则称 a 被 b 整除,或 b 整除 a,记作 \(b \mid a\)。此时,又称 a 是 b 的倍数,b 是 a 的因子,这个性质奠定了数论的基础。
设 a,b 是两个正整数,且 b 不为 0,那么有唯一地整数 q 和 r,使得 \(a = qb + r (0 ≤ r < |b|)\)。这个式子叫做带余除法,并记余数 \(r = a \% b\)。
整除的性质
\(1.\) 若 \(a | b\) 且 \(a|c\),则有 \(a | xb+yc\)
\(2.\) 若 \(a | b\) 且 \(b|c\),则 \(a | c\)
\(3.\) 设 \(m \neq 0\),则 \(a|b\),当且仅当 \(ma | mb\)
\(4.\) 若 \(a | b\) 且 \(b | a\),则 \(a = ±b\)
数的整除特征
1 与 0 的特性:
1 是任何整数的约数,即对于任何整数 a,总有 \(1 | a\)。
0 是任何非零整数的倍数,\(a \neq 0\),a 为整数,则 \(a | 0\)。
能被 2, 5 ; 4, 25 ; 8, 125 ; 3, 9 ; 11, 7, 13 整除的数的特征:
能被 2 整除的数: 个位为 0, 2, 4, 6, 8 的整数能被 2 整除,我们记为 2k(k 为整数)
能被 5 整除的数: 个位为 0 或 5 的整数必被 5 整除,我们记为 5k(k 为整数)
能被 4, 25 整除的数: 末两位数字组成的两位数能被 4(25)整除的整数必能被 4(25)整除
能被 8, 125 整除的数: 末三位数字组成的三位数能被 8(125)整除的整除必能被 8(125)整除
能被 3, 9 整除的数: 各个数位上数字之和能被 3 或 9 整除的整数必能被 3 或 9 整除
能被 11 整除的数: 一个整数的奇数位数字之和与偶数位数字之和的差如果是 11 的倍数,则这个数就能被 11 整除
能被 7, 11, 13 整除的数: 一个三位以上的整数能被 7(11 或 13)整除只须看这个数的末三位数字表示的三位数与末三位以前的数字组成的数之差(以大减小)能否被 7(11 或 13)整除
约数
算数基本定理的推论:在算数基本定理中,若正整数 n 被唯一分解为 \(n=p1^{a1}p2^{a2}...pn{an}\),其中 \(ai\) 数正整数,\(pi\) 是质数,且满足 \(p1 < p2 <...<pn\),其中 a 是正整数,p 是质数
则 n 的正约数集合可写作:{ $ p1{b1}p2...pn^{bn}$ },其中 \(0 ≤ b ≤ ai\)。n 的正约数个数为(\(\prod\) 表示乘积符号)
\((a1+1)(a2+1)...(an+1)= \prod_{i=1}^n(ai+1)\)
n 的所有正约数的和为:
\((1+p1+p1^2+...+p1^n) * ... * (1+pn+pn^2+...+pn^{an})\)
求 n 的正约数集合
1.试除法
若 \(d≥ \sqrt{n}\) 是 n 的约数,则 \(\frac{n}{d}≤ \sqrt{n}\) 也是 n 的约数。换而言之,约数总是成对出现的(除了对于完全平方数,\(\sqrt{n}\) 会单独出现),因此,只需要扫描 \(d=1\) 到 \(\sqrt{n}\),尝试 d 能否整除 n,若能整除,则 \(\frac{n}{d}\) 也是 n 的约数,算法时间复杂度为 \(O( \sqrt{n})\)
Code
void solve()
{
int factor[1600], m = 0;
for(int i = 1; i * i <= n; i++)
if(n % i == 0)
{
factor[++m] = i;
if(i != n / i) factor[++m] = n / i;
}
for(int i = 1; i <= m; i++) cout << factor[i] << endl;
}
2.倍数法
若用“试除法”分别求出 1 到 n 每个数的正约数集合,算法的时间复杂度过高,为 \(O(n * \sqrt{n})\)。有没有更高效的方法呢?
这时可以反过来考虑,对于每个数 d,1 到 n 中以 d 为约数的数就是 \(d, 2d, 3d...\lfloor{\frac{n}{d}} \rfloor * d\),即采用“倍数法”求出 1 到 n 每个数的正约数集合。算法的时间复杂度为 \(O(n+\frac{n}{2}+\frac{n}{3}+...+\frac{n}{n}) = O(n log_n)\)
1 到 n 每个数的约数个数总和大约为 \(n log_n\)
例题
1.反素数
然而并不会做。。。

浙公网安备 33010602011771号