整除/gcd

设a,b两个整数,如果存在整数c使得a=bc,则称b整除a,又称ab的倍数,ba的因子,记作a|b.
设两个整数a,b,且b0,则存在唯一的整数qr使得
a=qb+r,0r<|b|

整数相关性质

性质1:
a|c,b|cab|c
性质2:
a|b,b|ca|c
性质3:
m0,则
a|bma|mb
性质4:
a|b,b|aa=±b

素数/合数

应该没人不知道什么是素数
pass

不知道该叫什么定理

合数 m 的最小的不等于 1 的正因子p一定是素数,且pm.

伯特兰-切比雪夫定理

设整数n>3,至少存在一个素数p满足n<p<2n2.
证明略,有兴趣可以自己搜

算数基本定理

a>1,则
n=p1r1p2r2p3r3pkrk
其中p1,p2,p3,,pk是不相同的质数,r1,r2,r3,,rk是正整数,且在不计顺序的情况下该表示是唯一的

定理中的表达式叫做a的质因数分解

证明
首先用数学归纳法证明𝑛可以分解成一些素数的乘积。
当𝑛 = 2时,因为 2 是素数,结论成立。
假设小于𝑛且大于 1 的整数都可以分解成一些素数的乘积.
对于nn是素数则结论成立。否则,若n是一个合数,n存在最小正素因子𝑝1,设n=p1n1,则 1<n1<n,根据归纳假设,𝑛1可以分解成一些素数的乘积,不妨设n1=p2p3ps可得n=p1p2p3ps
接着利用反证法证明分解的唯一性

公约数、公倍数

a,b是两个整数,如果d|ad|b,则称dab的公因子或公约数,
其中最大的称作最大公约数记作gcd(a,b)
a,b是两个整数,如果a|db|d,则称dab的公倍数,
其中最小的称作最小公倍数记作lcm(a,b)

欧几里得算法求gcd

1
2
3
int gcd(int a, int b) {
return !b ? a : gcd(b, a % b);
}

lcm(a,b)=ab/gcd(a,b)

裴蜀定理

a,b是不全为0的整数,则存在整数x,y使得ax+by=gcd(a,b)

给出大致证明

  1. 如果a,b中有0,显然成立
  2. a,b0
    由于gcd(a,b)=gcd(a,b),不妨设a,b>0,ab,gcd(a,b)=d
    ax+by=d两边同时除以d,可得a1x+b1y=1,其中(a1,b1)=1
    现在只需要证a1x+b1y=1
    回顾gcd(a,b)gcd(a,b)gcd(b,amodb)
    gcd(a1,b1)=gcd(b1,r1)=gcd(r1,r2)==(rn1,rn)=1
    到了(rn1,rn)互质的时候开始往回推,消去r1 rn2,就可以证得1=a1+b1y

扩展欧几里得

常用于求 ax+by=gcd(a,b) 的一组可行解。
然后根据该方程的等式关系进行参数变换

1
2
3
4
5
6
7
8
9
10
11
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}

同余

什么是同余?
QQ截图20230912203158.jpg

欧拉定理

m为正整数,gcd(a,m)=1,那么aφ(m)1(modm)
证明比较麻烦,涉及到比较的概念,可以看看相关教材

线性同余方程

形如axb(modn)的方程称为线性同余方程。其中,abn 为给定整数,x 为未知数。需要从区间 [0,n1] 中求解 x,当解不唯一时需要求出全体解。

扩展欧几里得求解

方程可变为axmy=b,扩欧求x即可

逆元求解

什么是逆元?
如果一个线性同余方程 ax1(modb),则 x 称为 amodb 的逆元,记作 a1

  • 求逆元
    1. 扩展欧几里得,原理和扩欧求线性同余方程一样
    2. 快速幂法:依赖于费马小定理 xab2(modb)
  • 线性求n个数逆元—利用逆元性质公式转化
    1
    2
    3
    4
    5
    s[0] = 1;
    for (int i = 1; i <= n; ++i) s[i] = s[i - 1] * a[i] % p;
    sv[n] = qpow(s[n], p - 2);
    for (int i = n; i >= 1; --i) sv[i - 1] = sv[i] * a[i] % p;
    for (int i = 1; i <= n; ++i) inv[i] = sv[i] * s[i - 1] % p;

来到例题小凯的疑惑

埃氏筛

1
2
3
4
5
6
7
8
9
10
11
12
13
int primes[N], cnt;
bool st[N];

void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}

线性筛

只被筛一次,被最小质因数筛

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int primes[N], cnt;
bool st[N];

void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}

欧拉函数

欧拉函数,即 φ(n),表示的是小于等于nn互质的数的个数。
比如说 φ(1)=1
n是质数的时候,显然有φ(n)=n1

1
2
3
4
5
6
7
8
9
10
11
12
13
int phi(int x)
{
int res = x;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
res = res / i * (i - 1);
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);

return res;
}

利用筛求欧拉函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int primes[N], cnt;
int euler[N];
bool st[N];

void euler_(int n)
{
euler[1] = 1;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j ++ )
{
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0)
{
euler[t] = euler[i] * primes[j];
break;
}
euler[t] = euler[i] * (primes[j] - 1);
}
}
}

中国剩余定理(CRT)

中国剩余定理可求解如下形式的一元线性同余方程组n1,n2,,nk):
{xa1(modn1) xa2(modn2)  xak(modnk) 
M=n1n2nk有唯一解
xi=1kaiMni(modni)(modM)

证明
存在性:

xi=1kaiMni(Mni)1(modni)(modM)
对任意1jk,有
xi=1kaiMni(Mni)1(modni)(modnj)
因为n1,n2,,nk两两互质,所以当i=j时,gcd(Mni,nj)=1
aiMni(Mni)1(modni)ai(modnk)
ijgcd(Mni,nj)=nj
aiMni(Mni)1(modni)0(modnk)
故,存在性得证。

唯一性:不会

求解过程:按照定理内容模拟就是
计算所有模数的积M
对于第 i 个方程:
计算 mi=Mni
计算 mi在模 ni 意义下的逆元 mi1
计算 ci=mi1(不要对 n_i 取模)。
方程组在模 M 意义下的唯一解为:x=i=1kaici(modn)

1
2
3
4
5
6
7
8
9
10
LL CRT(int k, LL* a, LL* r) {
LL n = 1, ans = 0;
for (int i = 1; i <= k; i++) n = n * r[i];
for (int i = 1; i <= k; i++) {
LL m = n / r[i], b, y;
exgcd(m, r[i], b, y); // b * m mod r[i] = 1
ans = (ans + a[i] * m * b % n) % n;
}
return (ans % n + n) % n;
}

数论分块

数论分块,又叫整除分块,数论分块可以快速计算一些含有除法向下取整的和式(即形如 i=1nf(i)g(ni) 的和式)。当可以在O(1)内计算f(r)f(l)或已经预处理出 f 的前缀和时,数论分块就可以在 O(n)的时间内计算上述和式的值。

给出一个结论,给定一个整数n,则对于nii属于正整数且小于n,有2n种取值

用数学语言表示就是
$$
\forall n \in \mathbb{N}{+}, \left|\left{ \lfloor \frac{n}{d} \rfloor \mid d \in \mathbb{N}{+},d\leq n \right}\right| \leq \lfloor 2\sqrt{n} \rfloor
$$

这也表示对于ni 的结果可以分成值相同的一段段,从而完成快速求和,这就是数论分块

1
2
3
4
5
6
for(int l = 1; l <= n; l++)
{
int d = n / l, r = n / d;
// 表示[l,r]这一段 n / x 都 == d
l = r;
}

如何快速求和UVa11526 H(n)

1
2
3
4
5
6
for(int l = 1; l <= n; l++)
{
int d = n / l, r = n / d;
sum += (r - l + 1) * d;
l = r;
}

给出一道最近遇到的数论分块Small Products

大致题意
求长度为K的序列中,由正整数组成,且任意两个相邻元素的乘积最多为N,模数为 109+7 的序列个数。

首先,长度为K的序列和任意两个相邻元素的乘积,如果前面确定了一个数x(x<n),后面接着的数必然是nx,然后往后面递推,可用前缀和优化
$$
dp_{i,j} = \sum {x=1} ^{\left\lfloor\dfrac {n}{j}\right\rfloor} f{i-1,x}
$$
时间复杂度O(NK)
这么做肯定不行,既然说了要用数论分块,肯定是对nj下手

考虑 nx=ny=t,xy
则$dp_{i,x} = dp_{i,y} = \sum {j=1} ^{t} dp{i-1, j}\left\lfloor\dfrac {n}{j}\right\rfloor,dp_{i}{j}$
dp_{i,j} = \sum {x=1} ^{cnt} dp{i-1,块的下标} * len_{x}
$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<bits/stdc++.h>
using namespace std;
#define int long long
int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}

int n, tot, k, cnt[100010], len[100010];
int dp[110][100010];
const int mod = 1e9 + 7;

signed main()
{
int n = read(), k = read();
map<int, int> mp;
for(int l = 1; l <= n; l++)
{
int d = n / l, r = n / d;
cnt[++tot] = r;
mp[r] = tot; // r对应的块的下表
len[tot] = r - l + 1;
l = r;
}
for(int i = 1; i <= tot; i++ ) dp[0][i] = 1;
for(int i = 1; i <= k; i++)
for(int j = 1; j <= tot; j++)
{
dp[i][j] = (dp[i][j - 1] + len[j] * dp[i - 1][mp[n / cnt[j]]]) % mod;
}
cout << dp[k][tot];
return 0;
}

然后来一点简单点的例题()