》》b站视频链接-郑州大学ACM实验室寒假新生培训Day5《《
》》b站视频链接-郑州大学ACM实验室寒假新生培训Day11《《
目录:
最近更新:2021.09.16
OP
在寒假直播的第五天我才发现这么一个宝藏教程,便推给了群里的大佬们,那时候正在忙牛客寒假赛,打算开学后再说,结果ph大佬竟然开学没多久就刷完了!!我反而没抽出空,直到最近才开始补~
留下此笔记,一是为了记录和理解知识点,把自己的知识点穿成线;二是充实一下博客,不至于比赛允许携带纸质材料时不知道印些什么,也不至于同学吐槽我的博客 太水了~
前四天直播的内容分别是:ACM及实验室简介,C++常用STL讲解,模拟、枚举、递推、递归,二分;
如果需要看可以在up主的投稿里面找;
此篇文章包括了Day5的内容,Day11的部分内容,和清华大学出版社《离散数学(第三版)》中的一些内容;
希望能够帮助到大家,如有发现错漏,欢迎评论更正,感激不尽;
快速幂
求
a
b
m
o
d
m
a^b\mod m
abmodm
(
b
⩽
1
e
18
)
(b\leqslant1e18)
(b⩽1e18);
PS: 这个蓝书一开头就讲了,印象很深
PS2:满足结合律的运算均可以用此类方法加速
对于任意的 b ,我们均可以将其转化为二进制表示,因此,对于 b ,我们有等式:
b = k 0 ⋅ 2 0 + k 1 ⋅ 2 1 + k 2 ⋅ 2 2 + . . . + k i ⋅ 2 i ; b=k_0\cdotp2^0+k_1\cdotp2^1+k_2\cdotp2^2+...+k_i\cdotp2^i; b=k0⋅20+k1⋅21+k2⋅22+...+ki⋅2i;
将其代入 a b a^b ab,依据高中数学则有:
a
b
=
a
k
0
⋅
2
0
+
k
1
⋅
2
1
+
k
2
⋅
2
2
+
.
.
.
+
k
i
⋅
2
i
a^b=a^{k_0\cdotp2^0+k_1\cdotp2^1+k_2\cdotp2^2+...+k_i\cdotp2^i}
ab=ak0⋅20+k1⋅21+k2⋅22+...+ki⋅2i
=
(
a
2
0
)
k
0
⋅
(
a
2
1
)
k
1
⋅
(
a
2
2
)
k
2
⋅
.
.
.
⋅
(
a
2
i
)
k
i
;
=(a^{2^0})^{k_0}\cdotp(a^{2^1})^{k_1}\cdotp(a^{2^2})^{k_2}\cdotp...\cdotp(a^{2^i})^{k_i};
=(a20)k0⋅(a21)k1⋅(a22)k2⋅...⋅(a2i)ki;
其中,
k
∈
{
0
,
1
}
k\in\{0,1\}
k∈{0,1};
而且有
(
a
2
i
)
=
(
a
2
i
−
1
)
2
(a^{2^i})=(a^{2^{i-1}})^2
(a2i)=(a2i−1)2;
那么我们在程序运行过程中,只需要维护好两个值,记录 b 取前 i 位时 ab 的值的 ans,以及记录 ( a 2 i ) (a^{2^i}) (a2i) 的值的变量 a ;
二进制数字 k i . . . k 2 k 1 k 0 ‾ \overline{k_i...k_2k_1k_0} ki...k2k1k0 正是b的二进制表示,所以程序运行中仅需要判断 b 的二进制第 i 位是否为1,以决定 ( a 2 i ) (a^{2^i}) (a2i) 是否乘入ans;
时间复杂度 O ( log b ) O(\log b) O(logb)
代码
const ll M=1e9+7;
ll qm(ll a,ll b)//qm即quick_mod
{
ll ans=1%M;//特殊处理M=1这一特殊情况
for(;b;b>>=1)
{
if(b&1)ans=a*ans%M;//如果b此位为1
a=a*a%M;
}
return ans;
}
矩阵快速幂
由于矩阵乘法同样满足结合律,所以矩阵乘幂也可以通过快速幂优化;
我们可以将矩阵以结构体中数组的方式存起来,方便传值;
struct mtx{
int a[102][102];
};
由线性代数基础知识,得矩阵乘法 ( A B ) (AB) (AB) 代码如下;
mtx mul(mtx A,mtx B)
{
mtx C;
memset(C.a,0,sizeof(C.a));
for(int i=0;i<100;i++)
for(int j=0;j<100;j++)
for(int k=0;k<100;k++)
{
C.a[i][j]+=A.a[i][k]*B.a[k][j];
}
return C;
}
快速幂计算矩阵幂次 ( A b ) (A^b) (Ab) 的代码如下:
mtx mqm(mtx A,int b)
{
mtx ans;
memset(ans.a,0,sizeof(ans.a));
for(int i=0;i<100;i++)ans.a[i][i]=1;
for(;b;b>>=1)
{
if(b&1)ans=mul(ans,A);
A=mul(A,A);
}
return ans;
}
以上代码没有考虑取模,取模在mul
函数中添加即可;
应用
例1
求斐波那契数列第 n 项对 M 取模之后的值( n ⩽ 1 e 18 n\leqslant1e18 n⩽1e18)
对于矩阵 [ a b ] \begin{bmatrix}a&b\end{bmatrix} [ab] ,右乘矩阵 [ 0 1 1 1 ] \begin{bmatrix}0&1\\1&1\end{bmatrix} [0111] ,可得矩阵 [ b a + b ] \begin{bmatrix}b&a+b\end{bmatrix} [ba+b] ;
类似地,我们可以依照斐波那契的递推公式获得以下结论
[ f ( n − 2 ) f ( n − 1 ) ] ⋅ [ 0 1 1 1 ] = [ f ( n − 1 ) f ( n ) ] \begin{bmatrix}f(n-2)&f(n-1)\end{bmatrix}\cdot\begin{bmatrix}0&1\\1&1\end{bmatrix}=\begin{bmatrix}f(n-1)&f(n)\end{bmatrix} [f(n−2)f(n−1)]⋅[0111]=[f(n−1)f(n)]
以此类推:
[ f ( n − 2 ) f ( n − 1 ) ] [ 0 1 1 1 ] m = [ f ( n − 2 + m ) f ( n − 1 + m ) ] \begin{bmatrix}f(n-2)&f(n-1)\end{bmatrix}\begin{bmatrix}0&1\\1&1\end{bmatrix}^m=\begin{bmatrix}f(n-2+m)&f(n-1+m)\end{bmatrix} [f(n−2)f(n−1)][0111]m=[f(n−2+m)f(n−1+m)]
运用此规律,便可以通过矩阵快速幂取模快速计算斐波那契数列给定项的值;
注:斐波那契数列有封闭公式 f ( n ) = ( 1 + 5 2 ) n − ( 1 − 5 2 ) n 5 , ( n ⩾ 1 ) f(n)=\frac {(\frac {1+\sqrt 5}2)^n-(\frac {1-\sqrt 5}2)^n} {\sqrt 5},(n\geqslant1) f(n)=5(21+5)n−(21−5)n,(n⩾1),可近似为 f ( n ) = ( 1 + 5 2 ) n 5 , ( n ⩾ 1 ) f(n)=\frac {(\frac {1+\sqrt 5}2)^n} {\sqrt 5},(n\geqslant1) f(n)=5(21+5)n,(n⩾1) ;
例1的拓展
给定常系数线性齐次递推方程如下
{
H
(
n
)
=
a
1
H
(
n
−
1
)
+
a
2
H
(
n
−
2
)
+
.
.
.
+
a
k
H
(
n
−
k
)
H
(
0
)
=
b
0
,
H
(
1
)
=
b
1
,
H
(
2
)
=
b
2
,
.
.
.
,
H
(
k
−
1
)
=
b
k
−
1
\begin{cases} H(n)=a_1H(n-1)+a_2H(n-2)+...+a_kH(n-k)&\\ H(0)=b_0,H(1)=b_1,H(2)=b_2,...,H(k-1)=b_{k-1} \end{cases}
{H(n)=a1H(n−1)+a2H(n−2)+...+akH(n−k)H(0)=b0,H(1)=b1,H(2)=b2,...,H(k−1)=bk−1
这个递推方程除了用递推方程求解的方法处理,还可以用矩阵乘法表示成:
[
H
(
n
−
k
)
⋯
H
(
n
−
2
)
H
(
n
−
1
)
]
[
0
0
⋯
0
0
a
k
1
0
⋯
0
0
a
k
−
1
0
1
⋯
0
0
a
k
−
2
⋮
⋮
⋱
⋮
⋮
⋮
0
0
⋯
1
0
a
2
0
0
⋯
0
1
a
1
]
m
\begin{bmatrix} H(n-k)&\cdots&H(n-2)&H(n-1) \end{bmatrix} \begin{bmatrix} 0&0&\cdots&0&0&a_{k}\\ 1&0&\cdots&0&0&a_{k-1}\\ 0&1&\cdots&0&0&a_{k-2}\\ \vdots&\vdots&\ddots&\vdots&\vdots&\vdots\\ 0&0&\cdots&1&0&a_2\\ 0&0&\cdots&0&1&a_1 \end{bmatrix}^m
[H(n−k)⋯H(n−2)H(n−1)]⎣⎢⎢⎢⎢⎢⎢⎢⎡010⋮00001⋮00⋯⋯⋯⋱⋯⋯000⋮10000⋮01akak−1ak−2⋮a2a1⎦⎥⎥⎥⎥⎥⎥⎥⎤m
= [ H ( n − k + m ) ⋯ H ( n − 2 + m ) H ( n − 1 + m ) ] =\begin{bmatrix} H(n-k+m)&\cdots&H(n-2+m)&H(n-1+m) \end{bmatrix} =[H(n−k+m)⋯H(n−2+m)H(n−1+m)]
还找到了一个转置之后的版本,看起来比上面的方便阅读:
图源
例2
给定a,b,求 ∑ i = a b F ( i ) \sum_{i=a}^bF(i) ∑i=abF(i) ( F ( i ) F(i) F(i) 为斐波那契数列第 i 项)对M取模的值;
设
S
(
n
)
=
∑
i
=
0
n
F
(
i
)
=
S
(
n
−
1
)
+
F
(
n
−
1
)
+
F
(
n
−
2
)
S(n)=\sum_{i=0}^nF(i)=S(n-1)+F(n-1)+F(n-2)
S(n)=∑i=0nF(i)=S(n−1)+F(n−1)+F(n−2) ;
这样我们就可以构造矩阵了;
[ 1 1 0 0 1 1 0 1 0 ] [ S ( i ) F ( i ) F ( i − 1 ) ] = [ S ( i + 1 ) F ( i + 1 ) F ( i ) ] \begin{bmatrix} 1&1&0\\ 0&1&1\\ 0&1&0 \end{bmatrix} \begin{bmatrix} S(i)\\ F(i)\\ F(i-1) \end{bmatrix}= \begin{bmatrix} S(i+1)\\ F(i+1)\\ F(i) \end{bmatrix} ⎣⎡100111010⎦⎤⎣⎡S(i)F(i)F(i−1)⎦⎤=⎣⎡S(i+1)F(i+1)F(i)⎦⎤
进一步地:
[ 1 1 0 0 1 1 0 1 0 ] n [ S ( 1 ) F ( 1 ) F ( 0 ) ] = [ S ( 1 + n ) F ( 1 + n ) F ( n ) ] \begin{bmatrix} 1&1&0\\ 0&1&1\\ 0&1&0 \end{bmatrix}^n \begin{bmatrix} S(1)\\ F(1)\\ F(0) \end{bmatrix}= \begin{bmatrix} S(1+n)\\ F(1+n)\\ F(n) \end{bmatrix} ⎣⎡100111010⎦⎤n⎣⎡S(1)F(1)F(0)⎦⎤=⎣⎡S(1+n)F(1+n)F(n)⎦⎤
接下来求出 S ( b ) − S ( a − 1 ) S(b)-S(a-1) S(b)−S(a−1) 即可;
当然,我们也可以推得 S ( n ) = f ( n + 2 ) − f ( 1 ) S(n)=f(n+2)-f(1) S(n)=f(n+2)−f(1) ;
S ( n ) = f ( 0 ) + f ( 1 ) + ∑ i = 2 n f ( i ) = f ( 0 ) + f ( 1 ) + ∑ i = 2 n f ( i − 1 ) + ∑ i = 2 n f ( i − 2 ) = f ( 0 ) + f ( 1 ) + ( S ( i − 1 ) − f ( 0 ) ) + S ( i − 2 ) = f ( 1 ) + S ( n − 1 ) + S ( n − 2 ) ∵ S ( n ) = S ( n − 1 ) + f ( n ) ∴ f ( n ) = f ( 1 ) + S ( n − 2 ) ∴ S ( n ) = f ( n + 2 ) − f ( 1 ) S(n)=f(0)+f(1)+\sum_{i=2}^nf(i)\\ =f(0)+f(1)+\sum_{i=2}^nf(i-1)+\sum_{i=2}^nf(i-2)\\ \ \\ =f(0)+f(1)+(S(i-1)-f(0))+S(i-2)\\ \ \\ =f(1)+S(n-1)+S(n-2)\\ \ \\ \because S(n)=S(n-1)+f(n)\\ \ \\ \therefore f(n)=f(1)+S(n-2)\\ \ \\ \therefore S(n)=f(n+2)-f(1) S(n)=f(0)+f(1)+i=2∑nf(i)=f(0)+f(1)+i=2∑nf(i−1)+i=2∑nf(i−2) =f(0)+f(1)+(S(i−1)−f(0))+S(i−2) =f(1)+S(n−1)+S(n−2) ∵S(n)=S(n−1)+f(n) ∴f(n)=f(1)+S(n−2) ∴S(n)=f(n+2)−f(1)
故亦可求出 f ( b + 2 ) − f ( a + 1 ) f(b+2)-f(a+1) f(b+2)−f(a+1) ;
例3
定义 f ( n ) = x f ( n − 1 ) + y f ( n − 2 ) , S ( n ) = ∑ i = 0 n f ( i ) 2 f(n)=xf(n-1)+yf(n-2),S(n)=\sum_{i=0}^nf(i)^2 f(n)=xf(n−1)+yf(n−2),S(n)=∑i=0nf(i)2 , 求 S ( n ) S(n) S(n) ;
同样地,由于
S ( n ) = S ( n − 1 ) + f ( n ) 2 = S ( n − 1 ) + x 2 f ( n − 1 ) 2 + 2 x y f ( n − 1 ) f ( n − 2 ) + y 2 f ( n − 2 ) 2 S(n)=S(n-1)+f(n)^2=S(n-1)+x^2f(n-1)^2+2xyf(n-1)f(n-2)+y^2f(n-2)^2 S(n)=S(n−1)+f(n)2=S(n−1)+x2f(n−1)2+2xyf(n−1)f(n−2)+y2f(n−2)2
得到矩阵乘法形式
[ 1 x 2 y 2 2 x y 0 x 2 y 2 2 x y 0 1 0 0 0 x 0 y ] [ S ( i − 1 ) f ( i − 1 ) 2 f ( i − 2 ) 2 f ( i − 1 ) f ( i − 2 ) ] \begin{bmatrix} 1&x^2&y^2&2xy\\ 0&x^2&y^2&2xy\\ 0&1&0&0\\ 0&x&0&y \end{bmatrix} \begin{bmatrix} S(i-1)\\ f(i-1)^2\\ f(i-2)^2\\ f(i-1)f(i-2) \end{bmatrix} ⎣⎢⎢⎡1000x2x21xy2y2002xy2xy0y⎦⎥⎥⎤⎣⎢⎢⎡S(i−1)f(i−1)2f(i−2)2f(i−1)f(i−2)⎦⎥⎥⎤
= [ S ( i − 1 ) + x 2 f ( i − 1 ) 2 + y 2 f ( i − 2 ) 2 + 2 x y f ( i − 1 ) f ( i − 2 ) x 2 f ( i − 1 ) 2 + y 2 f ( i − 2 ) 2 + 2 x y f ( i − 1 ) f ( i − 2 ) f ( i − 1 ) 2 f ( i − 1 ) ( x f ( i − 1 ) + y f ( i − 2 ) ) ] = [ S ( i ) f ( i ) 2 f ( i − 1 ) 2 f ( i ) f ( i − 1 ) ] =\begin{bmatrix} S(i-1)+x^2f(i-1)^2+y^2f(i-2)^2+2xyf(i-1)f(i-2)\\ x^2f(i-1)^2+y^2f(i-2)^2+2xyf(i-1)f(i-2)\\ f(i-1)^2\\ f(i-1)(xf(i-1)+yf(i-2)) \end{bmatrix}= \begin{bmatrix} S(i)\\ f(i)^2\\ f(i-1)^2\\ f(i)f(i-1) \end{bmatrix} =⎣⎢⎢⎡S(i−1)+x2f(i−1)2+y2f(i−2)2+2xyf(i−1)f(i−2)x2f(i−1)2+y2f(i−2)2+2xyf(i−1)f(i−2)f(i−1)2f(i−1)(xf(i−1)+yf(i−2))⎦⎥⎥⎤=⎣⎢⎢⎡S(i)f(i)2f(i−1)2f(i)f(i−1)⎦⎥⎥⎤
同理
[ 1 x 2 y 2 2 x y 0 x 2 y 2 2 x y 0 1 0 0 0 x 0 y ] n [ S ( 1 ) f ( 1 ) 2 f ( 0 ) 2 f ( 1 ) f ( 0 ) ] = [ S ( 1 + n ) f ( 1 + n ) 2 f ( n ) 2 f ( 1 + n ) f ( n ) ] \begin{bmatrix} 1&x^2&y^2&2xy\\ 0&x^2&y^2&2xy\\ 0&1&0&0\\ 0&x&0&y \end{bmatrix}^n \begin{bmatrix} S(1)\\ f(1)^2\\ f(0)^2\\ f(1)f(0) \end{bmatrix}= \begin{bmatrix} S(1+n)\\ f(1+n)^2\\ f(n)^2\\ f(1+n)f(n) \end{bmatrix} ⎣⎢⎢⎡1000x2x21xy2y2002xy2xy0y⎦⎥⎥⎤n⎣⎢⎢⎡S(1)f(1)2f(0)2f(1)f(0)⎦⎥⎥⎤=⎣⎢⎢⎡S(1+n)f(1+n)2f(n)2f(1+n)f(n)⎦⎥⎥⎤
欧几里得算法
即辗转求余,C++中可以直接使用__gcd(a,b)
来求 a 与 b 的最大公约数;
若有
d
=
g
c
d
(
x
,
y
)
d=gcd(x,y)
d=gcd(x,y)
则有
d
∣
x
,
d
∣
y
;
d|x,d|y;
d∣x,d∣y;
由于
x
=
a
d
,
y
=
b
d
x=ad,y=bd
x=ad,y=bd
故
d
∣
(
x
−
y
)
;
d|(x-y);
d∣(x−y);
则有
g
c
d
(
x
,
y
)
=
g
c
d
(
x
−
y
,
y
)
;
gcd(x,y)=gcd(x-y,y);
gcd(x,y)=gcd(x−y,y);
以此类推,
g
c
d
(
x
,
y
)
=
g
c
d
(
x
−
2
y
,
y
)
=
g
c
d
(
x
−
3
y
,
y
)
=
.
.
.
;
gcd(x,y)=gcd(x-2y,y)=gcd(x-3y,y)=...;
gcd(x,y)=gcd(x−2y,y)=gcd(x−3y,y)=...;
化作取模
g
c
d
(
x
,
y
)
=
g
c
d
(
x
%
y
,
y
)
;
gcd(x,y)=gcd(x\%y,y);
gcd(x,y)=gcd(x%y,y);
时间复杂度 O ( log m a x ( x , y ) ) O(\log max(x,y)) O(logmax(x,y))
关于时间复杂度
规律:每次计算后,最大数至少被折半;
证明:
假设较大数为x,较小数为y;
若
y
⩽
x
/
2
y\leqslant x/2
y⩽x/2 ,则有
x
%
y
⩽
y
⩽
x
/
2
;
x\%y\leqslant y\leqslant x/2;
x%y⩽y⩽x/2;
若
y
>
x
/
2
y\gt x/2
y>x/2 ,则有
x
%
y
=
x
−
y
<
x
/
2
;
x\%y=x-y<x/2;
x%y=x−y<x/2;
毕;
代码
贴一个c语言的吧,(实际上语法都一样)
ll gcd(ll x,ll y)
{
if(!y)return x;
return gcd(x%y,x);
}//在具体处理中,我将x放入递归函数的y中,避免持续对一个数求余
最大公约数与最小公倍数
有以下两条定理:
- 若 a ∣ m , b ∣ m a|m,b|m a∣m,b∣m ,则 l c m ( a , b ) ∣ m lcm(a,b)|m lcm(a,b)∣m ;
- 若 d ∣ a , d ∣ b d|a,d|b d∣a,d∣b ,则 d ∣ g c d ( a , b ) d|gcd(a,b) d∣gcd(a,b) ;
以上两条定理可由素因子分解(下文的唯一分解定理)简单证明;
整除分块
个人之前的题解(D)曾经用函数图形简略证明过这个问题;
规律:对于 ⌊ n k ⌋ ( k ∈ { 1 , 2 , . . . , n } ) \lfloor \frac n k\rfloor(k\in\{1,2,...,n\}) ⌊kn⌋(k∈{1,2,...,n}) ,不同的值有 2 ⌊ n ⌋ 2\lfloor\sqrt n\rfloor 2⌊n⌋ 或 2 ⌊ n ⌋ − 1 2\lfloor\sqrt n\rfloor-1 2⌊n⌋−1 个;
证明:
k
⩽
n
k\leqslant\sqrt n
k⩽n 时,显然此时函数
f
(
k
)
=
n
/
k
f(k)=n/k
f(k)=n/k 的导函数小于-1,即 k 每缩小1,n/x 的值增加多于1,故此时
⌊
n
k
⌋
/
=
⌊
n
k
−
1
⌋
\lfloor \frac n k\rfloor \mathrlap{\,/}{=}\lfloor \frac n {k-1}\rfloor
⌊kn⌋/=⌊k−1n⌋,此处有
⌊
n
⌋
\lfloor\sqrt n\rfloor
⌊n⌋种不同取值;
此时
⌊
n
k
⌋
m
i
n
⩾
⌊
n
⌋
\lfloor \frac n k\rfloor_{min}\geqslant\lfloor\sqrt n\rfloor
⌊kn⌋min⩾⌊n⌋ (仅
⌊
n
⌋
2
=
n
\lfloor\sqrt n\rfloor^2=n
⌊n⌋2=n 时取等)
k
>
n
k\gt \sqrt n
k>n 时,除可利用函数图像直观验证外,还可做以下证明:
此种情况
k
m
i
n
=
⌊
n
⌋
+
1
;
k_{min}=\lfloor \sqrt n\rfloor+1;
kmin=⌊n⌋+1;
由初中数学易验证
⌊
n
⌋
−
1
<
n
⌊
n
⌋
+
1
<
⌊
n
⌋
+
1
\lfloor\sqrt n\rfloor-1<\frac n {\lfloor \sqrt n\rfloor+1}<\lfloor\sqrt n\rfloor+1
⌊n⌋−1<⌊n⌋+1n<⌊n⌋+1,
那么
⌊ n ⌊ n ⌋ + 1 ⌋ = { ⌊ n ⌋ ( n ⩾ ⌊ n ⌋ 2 + ⌊ n ⌋ ) (such as 8 , 12 , 89 ) ⌊ n ⌋ − 1 ( n < ⌊ n ⌋ 2 + ⌊ n ⌋ ) (such as 9 , 10 , 91 ) \lfloor\frac n {\lfloor \sqrt n\rfloor+1}\rfloor= \begin{cases} \lfloor \sqrt n\rfloor\ &(n \geqslant \lfloor \sqrt n\rfloor^2+\lfloor \sqrt n\rfloor) \text{(such as }8,12,89)\\ \lfloor \sqrt n\rfloor-1\ & (n \lt \lfloor \sqrt n\rfloor^2+\lfloor \sqrt n\rfloor) \text{(such as }9,10,91) \end {cases} ⌊⌊n⌋+1n⌋={⌊n⌋ ⌊n⌋−1 (n⩾⌊n⌋2+⌊n⌋)(such as 8,12,89)(n<⌊n⌋2+⌊n⌋)(such as 9,10,91)
所以 ⌊ n ⌊ n ⌋ + 1 ⌋ , ⌊ n ⌊ n ⌋ + 2 ⌋ , . . . , ⌊ n n ⌋ \lfloor\frac n {\lfloor \sqrt n\rfloor+1}\rfloor,\lfloor\frac n {\lfloor \sqrt n\rfloor+2}\rfloor,...,\lfloor\frac n n\rfloor ⌊⌊n⌋+1n⌋,⌊⌊n⌋+2n⌋,...,⌊nn⌋ 的值均分布在 { 1 , 2 , . . . , ⌊ n ⌊ n ⌋ + 1 ⌋ } \{1,2,...,\lfloor\frac n {\lfloor \sqrt n\rfloor+1}\rfloor\} {1,2,...,⌊⌊n⌋+1n⌋}中,此处有 ⌊ n ⌋ \lfloor\sqrt n\rfloor ⌊n⌋ 或 ⌊ n ⌋ − 1 \lfloor\sqrt n\rfloor-1 ⌊n⌋−1 种不同取值;
由于 ⌊ n ⌋ 2 = n \lfloor\sqrt n\rfloor^2=n ⌊n⌋2=n 时, n < ⌊ n ⌋ 2 + ⌊ n ⌋ n \lt \lfloor \sqrt n\rfloor^2+\lfloor \sqrt n\rfloor n<⌊n⌋2+⌊n⌋ 恒成立,故不存在两种情况下取相同值的情况;
代码
题目:
求
∑
k
=
1
n
⌊
n
k
⌋
,
n
⩽
1
e
9
\sum^n_{k=1}\lfloor \frac n k \rfloor,n\leqslant1e9
∑k=1n⌊kn⌋,n⩽1e9;
for(int l=1,r=1;l<=n;l=r+1)//每一次循环对应一个分块的 l,r,n/l
{
r=n/(n/l);
ans+=n/l*(r-l+1);
}
n
l
\frac n l
ln 即为该分块的值;
同时,有
r
n
l
⩽
n
r\frac n l\leqslant n
rln⩽n;
为使 r 取到最大值,则可以对于一个 l ,直接取
r
=
n
n
/
l
r=\frac n {n/l}
r=n/ln;
欧拉筛
插一句,暴力判断一个数是否为质数的时间复杂度为
O
(
n
)
O(\sqrt n)
O(n)
求 1 ~ n 内的所有质数;
欧拉筛的特点:每个合数仅被其最小的质因子筛除,且仅被筛一次,时间复杂度 O ( n ) O(n) O(n);
代码
const ll N=1e7;
int p[N+7],c=0;//p[]存储第 i 个质数(from 0),c 作为末尾标记
bool n[N+7];//n[]存储整数 i 是否为质数,若标记 0 则为质数,标记 1 则为合数
void pri()
{
n[0]=n[1]=1;//0,1 均不是质数
for(int i=2;i<=N;i++)
{
if(!n[i])p[c++]=i;//若为质数,则加入 p 数组
for(int j=0;j<c&&i*p[j]<=N;j++)
{
n[i*p[j]]=1;
if(i%p[j]==0)break;
}
}
}
时间复杂度证明
维持线性时间复杂度的关键是语句if(i%p[j]==0)break;
;
对于每一个数被筛掉的情况,我手动模拟出了这样一张图
其中描述了合数是如何被筛掉的;
对于数字12,我们观察到其被2*6
筛掉,而不是3*4
,因为4在筛掉2*4=8
之后就break掉了;
所以,我们能观察到如下规律:
若合数
h
h
h 的最小质因数为
p
i
p_i
pi ,则对于合数
p
i
+
1
∗
h
p_{i+1}*h
pi+1∗h 一定有
p
i
∗
(
h
∗
p
i
+
1
/
p
i
)
p_i*(h*p_{i+1}/p_i)
pi∗(h∗pi+1/pi) ,其中,一定存在
p
i
<
p
i
+
1
p_i\lt p_{i+1}
pi<pi+1 ,所以以
p
i
+
1
∗
h
p_{i+1}*h
pi+1∗h 筛掉该合数不满足被最小质因数筛掉这一要求;
换言之,如果不在 i%p[j]==0
时break掉,则一定有数被筛掉多次,造成时间上的浪费;
综上,每个数仅被筛掉一次,时间复杂度
O
(
n
)
O(n)
O(n);
同时也可以获取到每个数的最小质因子;
再进行一点点优化
平常比赛时候一般用不到,只是了解过
来自此链接;
大意如下:
除2外每一个质数一定满足
6
n
±
1
6n\pm1
6n±1 ;
理由如下:
(
6
n
)
%
6
=
0
(6n)\%6=0
(6n)%6=0;
(
6
n
+
2
)
%
2
=
0
(6n+2)\%2=0
(6n+2)%2=0;
(
6
n
+
3
)
%
3
=
0
(6n+3)\%3=0
(6n+3)%3=0;
(
6
n
+
4
)
%
2
=
0
(6n+4)\%2=0
(6n+4)%2=0;
所以筛的时候只需要筛 n / 3 就好啦
素数的分布
有无穷多个素数;
记
π
(
n
)
\pi(n)
π(n) 为小于等于 n 的素数的个数,当
n
⩾
67
n\geqslant67
n⩾67 时:
ln
(
n
)
−
3
2
⩽
n
π
(
n
)
⩽
ln
(
n
)
−
1
2
\ln(n)-\frac 3 2\leqslant\frac n {\pi(n)}\leqslant\ln(n)-\frac 1 2
ln(n)−23⩽π(n)n⩽ln(n)−21
得推论(素数定理):
lim
n
→
+
∞
π
(
n
)
n
/
ln
n
=
1
\lim_{n\to+\infty}\frac {\pi(n)}{n/\ln n}=1
n→+∞limn/lnnπ(n)=1
整数的唯一分解定理
对于正整数 n ,仅存在一种分解方式将其分解为若干质数的乘积:
n = p 1 t 1 p 2 t 2 . . . p k t k n=p_1^{t_1}p_2^{t_2}...p_k^{t_k} n=p1t1p2t2...pktk
通常与欧拉筛搭配使用以降低时间复杂度;
代码
方法一:在筛出素数后再进行试除;
const ll N=1e7;
int p[N+7],c=0;
bool n[N+7];
void pri()
{
n[0]=n[1]=1;
for(int i=2;i<=N;i++)
{
if(!n[i])p[c++]=i;
for(int j=0;j<c&&i*p[j]<=N;j++)
{
n[i*p[j]]=1;
if(i%p[j]==0)break;
}
}
}
//以上为欧拉筛
int main()
{
pri();
int n;
while(cin>>n)
{
for(int i=0;i<c&&p[i]<=n;i++)
{
int t=0;
while(n%p[i]==0)
{
n/=p[i];
t++;
}
//此时此处t即为pi的幂次
}
}
return 0;
}
方法二:依照欧拉筛中每个数只会被自己的最小质因子标记
的性质,记录每个数的最小质因子,再递归处理
const ll N=1e7;
int p[N+7],minp[N+7],c=0;
bool n[N+7];
void pri()
{
n[0]=n[1]=1;
for(int i=2;i<=N;i++)
{
if(!n[i])p[c++]=i,minp[i]=1;
for(int j=0;j<c&&i*p[j]<=N;j++)
{
n[i*p[j]]=1;
minp[i*p[j]]=p[j];
if(i%p[j]==0)break;
}
}
}
//以上为欧拉筛
int main()
{
pri();
int n;
while(cin>>n)
{
while(n!=1)
{
//此时minp[n]即为n的最小质因子
n=minp[n];
}
}
return 0;
}
例题
例1:n 有多少个因子?
对于 n 的任何一个因子 m ,都可以表示为
m = p 1 t m 1 p 2 t m 2 . . . p k t m k m=p_1^{t_{m1}}p_2^{t_{m2}}...p_k^{t_{mk}} m=p1tm1p2tm2...pktmk
其中,
t
m
i
∈
[
0
,
t
i
]
t_{mi}\in[0,t_i]
tmi∈[0,ti];
所以,一共有
(
t
1
+
1
)
(
t
2
+
1
)
.
.
.
(
t
k
+
1
)
(t_1+1)(t_2+1)...(t_k+1)
(t1+1)(t2+1)...(tk+1) 个;
const ll N=1e7;
int p[N+7],c=0;
bool n[N+7];
void pri()
{
n[0]=n[1]=1;
for(int i=2;i<=N;i++)
{
if(!n[i])p[c++]=i;
for(int j=0;j<c&&i*p[j]<=N;j++)
{
n[i*p[j]]=1;
if(i%p[j]==0)break;
}
}
}
//以上为欧拉筛
int main()
{
pri();
int n;
while(cin>>n)
{
ll ans=1;
for(int i=0;i<c&&p[i]<=n;i++)
{
int t=0;
while(n%p[i]==0)
{
n/=p[i];
t++;
}
ans*=t+1;
}
printf("%lld\n",ans);
}
return 0;
}
只有这题是唯一分解定理板子,例二例三都不是
例2:n ! 有多少个因子?
由上题得,我们只需要将 n ! 表示为
n ! = p 1 t 1 p 2 t 2 . . . p k t k n!=p_1^{t_{1}}p_2^{t_{2}}...p_k^{t_{k}} n!=p1t1p2t2...pktk
即可求出因子数;
接下来我们只需要求出每一个质数的 ti ;
由求 n ! 末尾有多少个 0 一题
中,求取总共贡献 5 的个数的方法,即可类似地求得每一个质数的 ti ;
t i = ∑ j = 1 p i j ⩽ n ⌊ n p i j ⌋ t_i=\sum_{j=1}^{p_i^j\leqslant n}\lfloor \frac n {p_i^j}\rfloor ti=j=1∑pij⩽n⌊pijn⌋
具体原理不做赘述;
时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn);
例3: ∏ i = 1 n i ! \prod_{i=1}^{n}i! ∏i=1ni! 末尾有几个0?
首先明确,此题实际上是求在连乘值中,能拆出多少个 5 ;
对于
1
!
⋅
2
!
⋅
.
.
.
⋅
i
!
1!\cdot 2!\cdot ...\cdot i!
1!⋅2!⋅...⋅i! ,乘上的 5 有
i
−
4
i-4
i−4 个;
乘上的 10 有
i
−
9
i-9
i−9 个,其中每个10 有一个 5 ;
乘上的 15 有
i
−
14
i-14
i−14 个;
…
乘上的 25 有
i
−
24
i-24
i−24 个,其中每个 25 只取第一个 5 ;
…
所以,由等差数列求和,
1
!
⋅
2
!
⋅
.
.
.
⋅
i
!
1!\cdot 2!\cdot ...\cdot i!
1!⋅2!⋅...⋅i! 能贡献出
(
2
i
−
5
⌊
i
5
⌋
−
5
+
2
)
⌊
i
5
⌋
2
\frac {(2i-5\lfloor\frac i 5 \rfloor-5+2)\lfloor \frac i 5\rfloor} 2
2(2i−5⌊5i⌋−5+2)⌊5i⌋个“第一个” 5 ;
继续来说,
乘上的 25 有
i
−
24
i-24
i−24 个,其中每个 25 只取第二个 5 ;
乘上的 50 有
i
−
49
i-49
i−49 个,其中每个 50 只取第二个 5 ;
…
乘上的 125 有
i
−
124
i-124
i−124 个,其中每个 125 只取第二个 5 ;
…
同样,由等差数列求和,
1
!
⋅
2
!
⋅
.
.
.
⋅
i
!
1!\cdot 2!\cdot ...\cdot i!
1!⋅2!⋅...⋅i! 能贡献出
(
2
−
25
⌊
i
25
⌋
−
25
+
2
)
⌊
i
25
⌋
2
\frac {(2-25\lfloor\frac i {25} \rfloor-25+2)\lfloor \frac i {25}\rfloor} 2
2(2−25⌊25i⌋−25+2)⌊25i⌋个“第二个” 5 ;
…
以此类推,一共可以贡献出
s = ∑ j = 1 5 j ⩽ n ( 2 n − 5 j ⌊ n 5 j ⌋ − 5 j + 2 ) ⌊ n 5 j ⌋ 2 s=\sum_{j=1}^{5^j\leqslant n}\frac {(2n-5^j\lfloor\frac n {5^j}\rfloor-5^j+2)\lfloor\frac n {5^j}\rfloor} {2} s=j=1∑5j⩽n2(2n−5j⌊5jn⌋−5j+2)⌊5jn⌋
s个5(此时的 j 即为“第 j 个 5 ”);
化简得(其实没必要)
s = ∑ j = 1 5 j ⩽ n ( n + 2 − 5 j + n % 5 j ) ⌊ n 5 j ⌋ 2 s=\sum_{j=1}^{5^j\leqslant n}\frac {(n+2-5^j+n\%5^j)\lfloor\frac n {5^j}\rfloor} {2} s=j=1∑5j⩽n2(n+2−5j+n%5j)⌊5jn⌋
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll M=10007;
int main()
{
int n;
while(cin>>n)
{
ll five=5;
ll ans=0;
while(five<=n)
ans+=(n+2-five+n%five)*(n/five)/2,five=5*five;
printf("%lld\n",ans);
}
}
欧拉函数
欧拉函数
φ
(
n
)
\varphi(n)
φ(n) 表示
[
1
,
n
−
1
]
[1,n-1]
[1,n−1] 区间内,与 n 互质的数的个数;
定义有
φ
(
1
)
=
1
\varphi (1)=1
φ(1)=1;
例如
φ
(
8
)
=
4
,
(
1
,
3
,
5
,
7
)
\varphi (8)=4\text{ , }(1,3,5,7)
φ(8)=4 , (1,3,5,7);
若 n 为质数,则有
φ
(
n
)
=
n
−
1
\varphi(n)=n-1
φ(n)=n−1;
一般来说,有
φ ( x ) = x ∏ i = 1 n ( 1 − 1 p i ) \varphi(x)=x\prod^n_{i=1}(1-\frac 1 {p_i}) φ(x)=xi=1∏n(1−pi1)
其中p1, p2……pn为x的所有质因数,x是不为0的整数(可由容斥定理证明)
还有一条性质如下:(积性函数)
若
m
,
n
m,n
m,n 互质,则有
φ ( m ⋅ n ) = φ ( m ) ⋅ φ ( n ) \varphi(m\cdotp n)=\varphi(m)\cdotp\varphi(n) φ(m⋅n)=φ(m)⋅φ(n)
代码
log求取某数欧拉函数值
由连乘式和整数分解,代码如下:
//在欧拉筛后
ll gphi(ll g)
{
ll ans = g;
if (g == 1)
return 0;
int sq = sqrt(g);
for (int i = 0; i < c && p[i] <= sq; i++)
{
if (g % p[i] == 0)
{
ans = ans - ans / p[i];
while (g % p[i] == 0)
{
g /= p[i];
}
}
}
if (g > 1)
ans = ans - ans / g;
return ans;
}
线性求取范围内的欧拉函数值
由上面的公式,我们可以得到两个递推式:
(其中p1, p2……pn为x的所有质因数,x是不为0的整数)
φ
(
x
p
i
)
=
φ
(
x
)
p
i
(
i
⩽
n
)
\varphi(xp_i)=\varphi(x)p_i\text{ }(i\leqslant n)
φ(xpi)=φ(x)pi (i⩽n)
φ
(
x
p
j
)
=
φ
(
x
)
p
j
(
1
−
1
p
j
)
=
φ
(
x
)
⋅
φ
(
p
j
)
(
j
>
n
)
\varphi(xp_j)=\varphi(x)p_j(1-\frac 1 {p_j})=\varphi(x)\cdotp \varphi(p_j)\text{ }(j\gt n)
φ(xpj)=φ(x)pj(1−pj1)=φ(x)⋅φ(pj) (j>n)
由以上推论,代码实现可以与欧拉筛共同完成:
phi[1]= 1;
for(int i = 2 ; i < N ; i ++ )
{
if(!n[i])
{
prime[c++] = i;
phi[i] = i - 1;
}
for(int j = 0 ; j < c && (ll)i * prime[j] < N ; j ++ )
{
n[i * prime[j] ] = 1;
if(i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * phi[prime[j]];
}
}
例题
求 ∑ i = 1 n ∑ j = 1 n [ g c d ( i , j ) = 1 ] , n ⩽ 1 e 7 \sum_{i=1}^n\sum_{j=1}^n[gcd(i,j)=1],n\leqslant1e7 ∑i=1n∑j=1n[gcd(i,j)=1],n⩽1e7 ;
对于任意一个 i ,比 i 小的数中与其互质的数有 φ ( i ) \varphi(i) φ(i) 个,即有 2 φ ( i ) 2\varphi(i) 2φ(i) 个数对,所以结果为 2 ∑ i = 1 n φ ( i ) 2\sum_{i=1}^n\varphi(i) 2∑i=1nφ(i) ;
由于 φ ( i ) \varphi(i) φ(i) 可以在线性复杂度中求出,故时间复杂度可接受;
欧拉定理
若 n , a 为正整数,且 n , a 互质,则有
a φ ( n ) ≡ 1 ( m o d n ) a^{\varphi(n)}\equiv 1(\mod n) aφ(n)≡1(modn)
费马小定理
费马小定理是欧拉定理的一种特殊情况
如果 p 是一个质数,而整数 a 不是 p 的倍数,则有
a p − 1 ≡ 1 ( mod p ) a^{p-1}\equiv 1(\text{mod }p) ap−1≡1(mod p)
也可以表示成
a p ≡ a ( mod p ) a^{p}\equiv a(\text{mod }p) ap≡a(mod p)
欧拉降幂
由欧拉定理可推得在 a , n 互质时
a
b
≡
a
b
%
φ
(
n
)
(
m
o
d
n
)
a^b\equiv a^{b\%\varphi(n)}(\mod n)
ab≡ab%φ(n)(modn)
对于更广泛的情况,我们有
a b ≡ { a b % φ ( n ) g c d ( a , n ) = 1 a b g c d ( a , n ) ≠ 1 , b < φ ( n ) a b % φ ( n ) + φ ( n ) g c d ( a , n ) ≠ 1 , b ⩾ φ ( n ) m o d n a^b\equiv\begin{cases} a^{b\%\varphi(n)}&gcd(a,n)=1\\ a^b&gcd(a,n)\not=1,b\lt\varphi(n)\\ a^{b\%\varphi(n)+\varphi(n)}&gcd(a,n)\not=1,b\geqslant\varphi(n) \end{cases} \mod n ab≡⎩⎪⎨⎪⎧ab%φ(n)abab%φ(n)+φ(n)gcd(a,n)=1gcd(a,n)=1,b<φ(n)gcd(a,n)=1,b⩾φ(n)modn
但是在实际操作中,并不需要判断第一种情况,直接将其归入情况二、三即可;
裴蜀定理 / 贝祖定理
若 a , b 是整数,且 g c d ( a , b ) = d gcd( a , b )=d gcd(a,b)=d ,那么对于任意的整数 x , y , a x + b y ax+by ax+by 均是 d 的倍数,特别地,一定存在整数 x , y,使 a x + b y = d ax+by=d ax+by=d 成立;
推论1:若
a
x
+
b
y
=
1
ax+by=1
ax+by=1 有解,则有
g
c
d
(
a
,
b
)
=
1
gcd(a,b)=1
gcd(a,b)=1 ( a , b 互质);
推论2:若
a
x
+
b
y
=
m
ax+by=m
ax+by=m 有解,则 m 一定为
g
c
d
(
a
,
b
)
gcd(a,b)
gcd(a,b) 的若干倍;
扩展欧几里得算法
证明过程同时也参考了这个帖子;
扩展欧几里得算法是解决关于 x , y 的方程 a x + b y = c ax + by = c ax+by=c 的解的问题,如果 g c d ( a , b ) ∣ c gcd(a,b) |c gcd(a,b)∣c ,则有解(可能有多组),否则无解。
我们可以将这个问题归结为 求方程 a x + b y = g c d ( a , b ) ax + by = gcd(a,b) ax+by=gcd(a,b) 的解,若存在一组解 { x 0 , y 0 } \{x_0,y _0\} {x0,y0} ,则对于方程 a x + b y = k ∗ g c d ( a , b ) ax + by = k*gcd(a,b) ax+by=k∗gcd(a,b) ,解即为 { k x 0 , k y 0 } \{kx_0,ky_0\} {kx0,ky0} ;
若我们已经求得
{
x
1
,
y
1
}
\{x_1,y_1\}
{x1,y1} 满足
b
x
1
+
(
a
%
b
)
y
1
=
g
c
d
(
b
,
a
%
b
)
=
d
bx_1+(a\%b)y_1=gcd(b,a\%b)=d
bx1+(a%b)y1=gcd(b,a%b)=d;
上行方程左式可如此变形:
左式
=
b
x
1
+
(
a
−
b
⌊
a
b
⌋
)
y
1
=
a
y
1
+
b
(
x
1
−
⌊
a
b
⌋
y
1
)
=bx_1+(a-b\lfloor \frac a b \rfloor)y_1=ay_1+b(x_1-\lfloor \frac a b \rfloor y_1)
=bx1+(a−b⌊ba⌋)y1=ay1+b(x1−⌊ba⌋y1)
所以此时有
{ x = y 1 y = x 1 − ⌊ a b ⌋ y 1 \begin{cases}x=y_1\\ y=x_1-\lfloor \frac a b \rfloor y_1 \end{cases} {x=y1y=x1−⌊ba⌋y1
由此,我们便可以进行递归回代处理;
对于递归终点,与求 gcd 时的类似,为 b = 0 b=0 b=0 ,此时有解 x = 1 , y = 0 x=1,y=0 x=1,y=0;
代码
int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1,y=0;
return a;
}
int ans=exgcd(b,a%b,x,y);
int y1=y,x1=x;
x=y1;
y=x1-a/b*y1;
return ans;
}
返回值即为 g c d ( a , b ) gcd(a,b) gcd(a,b);
逆元
即模意义下的倒数(除法)
若 x 满足
a
x
≡
1
(
mod
m
)
ax\equiv 1(\text{mod } m)
ax≡1(mod m) ,则称 x 是 a 在 m 意义下的逆元;
x 应满足
0
⩽
x
<
m
0\leqslant x\lt m
0⩽x<m ;
逆元的意义:
经验来讲,逆元一般是在做取模运算下的除法时会用到;
对于三个整数 a , b , M ,取模的性质有
(
a
+
b
)
%
M
=
(
a
%
M
+
b
%
M
)
%
M
(a+b)\%M=(a\%M+b\%M)\%M
(a+b)%M=(a%M+b%M)%M ;
(
a
∗
b
)
%
M
=
(
(
a
%
M
)
∗
(
b
%
M
)
)
%
M
(a*b)\%M=((a\%M)*(b\%M))\%M
(a∗b)%M=((a%M)∗(b%M))%M ;
对除法则没有此类性质;
若我们有 x 是 a 在 M 意义下的逆元,
则有
(
b
/
a
)
%
M
=
(
b
∗
x
)
%
M
(b/a)\%M=(b*x)\%M
(b/a)%M=(b∗x)%M ;
扩展欧几里得法求逆元
由上一部分可知,对于关于 x , y 的方程
A
x
+
B
y
=
g
c
d
(
A
,
B
)
Ax+By=gcd(A,B)
Ax+By=gcd(A,B) 一定存在解;
将等式两边同时除以
g
c
d
(
A
,
B
)
gcd(A,B)
gcd(A,B) ,化作
a
x
+
b
y
=
1
ax+by=1
ax+by=1 ,此时
g
c
d
(
a
,
b
)
=
1
gcd(a,b)=1
gcd(a,b)=1;(此有解结论同样可以由裴蜀定理得出)
再将等式两侧同时对 b 取模,化作
a
x
≡
1
(
mod
b
)
ax\equiv 1(\text{mod } b)
ax≡1(mod b);
此方程同样有一定有解;
接下来,我们应该将某特解 { x , y } \{x,y\} {x,y} ,转换为使得 x 满足条件且最小的解;
若有
{
x
,
y
}
\{x,y\}
{x,y} 满足
a
x
+
b
y
=
1
ax+by=1
ax+by=1 ,同时即有
a
(
x
+
b
)
+
b
(
y
−
a
)
=
1
a(x+b)+b(y-a)=1
a(x+b)+b(y−a)=1;
所以,
{
x
+
b
,
y
−
a
}
\{x+b,y-a\}
{x+b,y−a} 也是原方程的解;
由此类推
{
x
+
k
b
,
y
−
k
a
}
\{x+kb,y-ka\}
{x+kb,y−ka} 是原方程的通解;
所以,对于一组互质的 a, b 和特解 x , y ,则满足条件的最小 x 可以表示为 ( x % b + b ) % b (x\%b+b)\%b (x%b+b)%b
以上,对于给定的一组互质的 a , M ,a的逆元x为 exgcd(a,M,x,y),x=(x%M+M)%M;
;
未证明:上面的exgcd板子代码中,返回的x若为正值,即为最小正数解
欧拉定理求逆元
PS:就以前的做题经验来看,个人几乎都是用这种方法求逆元
一般用于 M 为质数,且
a
<
M
a<M
a<M 时:
由于欧拉定理有
a φ ( M ) ≡ 1 ( mod M ) a^{\varphi(M)}\equiv 1(\text{mod }M) aφ(M)≡1(mod M)
由于 M 为质数,则有
a
M
−
1
≡
1
(
mod
M
)
a^{M-1}\equiv 1(\text{mod }M)
aM−1≡1(mod M)
a
⋅
a
M
−
2
≡
1
(
mod
M
)
a\cdot a^{M-2}\equiv 1(\text{mod }M)
a⋅aM−2≡1(mod M)
所以有
a
M
−
2
a^{M-2}
aM−2 为 a 在 M 意义下的逆元;
具体的值可以由快速幂求出;
递归求逆元
求 a 在模 p 意义下的逆元即求 a − 1 a^{-1} a−1 在模 p 意义下的值;
p = k a + r k a + r ≡ 0 ( m o d p ) k r − 1 + a − 1 ≡ 0 ( m o d p ) a − 1 ≡ − k r − 1 ( m o d p ) a − 1 ≡ − ⌊ p / a ⌋ ⋅ i n v ( p % a ) ( m o d p ) p=ka+r\\\text{ }\\ ka+r\equiv0(\mod p)\\\text{ }\\ kr^{-1}+a^{-1}\equiv0(\mod p)\\\text{ }\\ a^{-1}\equiv-kr^{-1}(\mod p)\\\text{ }\\ a^{-1}\equiv-\lfloor p/a\rfloor\cdotp inv(p\%a)(\mod p) p=ka+r ka+r≡0(modp) kr−1+a−1≡0(modp) a−1≡−kr−1(modp) a−1≡−⌊p/a⌋⋅inv(p%a)(modp)
由此我们可以递归实现求取
long long inv(long long a)
{
if(a==1)return 1;
return (mod-mod/a)*inv(mod%a)%mod;
}
代码中多加入了防负的部分
线性求逆元
依据上面的公式,同样可以得出线性求逆元的代码:
ll inv[N+5];
void ginv()
{
inv[1]=1;
for(int i=2;i<=N;i++)
inv[i]=(M-M/i)*inv[M%i]%M;
}
线性求逆元主要用于需要使用大量数的逆元时;
需要注意的是,虽然 i n v [ ] inv[] inv[] 数组中每个下标都有值,但不是每个下标的值都是其逆元,有些数是没有模M意义下的逆元的;
莫比乌斯函数
莫比乌斯函数定义有
μ ( n ) { 1 n = 1 ( − 1 ) k n 无 平 方 因 数 , 且 n = p 1 p 2 . . . p k 0 n 有 大 于 1 的 平 方 因 数 \mu(n)\begin{cases} 1&n=1\\ (-1)^k&n无平方因数,且n=p_1p_2...p_k\\ 0&n有大于1的平方因数 \end{cases} μ(n)⎩⎪⎨⎪⎧1(−1)k0n=1n无平方因数,且n=p1p2...pkn有大于1的平方因数
与欧拉函数类似,莫比乌斯函数同样为积性函数:
若 a , b 互质,则有
μ ( a ⋅ b ) = μ ( a ) ⋅ μ ( b ) \mu(a\cdotp b)=\mu(a)\cdotp\mu(b) μ(a⋅b)=μ(a)⋅μ(b)
还有如下性质
∑ d ∣ n μ ( d ) = [ n = 1 ] \sum_{d|n}\mu(d)=[n=1] d∣n∑μ(d)=[n=1]
代码
与欧拉筛共同完成:
mu[1]= 1;
for(int i = 2 ; i < N ; i ++ )
{
if(!n[i])
{
prime[c++] = i;
mu[i] = -1;
}
for(int j = 0 ; j < c && (ll)i * prime[j] < N ; j ++ )
{
n[i * prime[j] ] = 1;
if(i % prime[j] == 0)
{
mu[i * prime[j]] = 0;//不互质,存在平方因子
break;
}
mu[i * prime[j]] = -mu[i];//互质,质数的莫比乌斯函数值为1
}
}
莫比乌斯反演
此部分参考了这个视频
设两函数 F ( n ) , f ( n ) F(n),f(n) F(n),f(n) 有以下两种形式
F ( n ) = ∑ d ∣ n f ( n ) ⇒ f ( n ) = ∑ d ∣ n μ ( d ) F ( n d ) F ( n ) = ∑ n ∣ d f ( n ) ⇒ f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) F(n)=\sum_{d|n}f(n)\Rightarrow f(n)=\sum_{d|n}\mu(d)F(\frac n d)\\\text{ }\\ F(n)=\sum_{n|d}f(n)\Rightarrow f(n)=\sum_{n|d}\mu(\frac d n)F(d) F(n)=d∣n∑f(n)⇒f(n)=d∣n∑μ(d)F(dn) F(n)=n∣d∑f(n)⇒f(n)=n∣d∑μ(nd)F(d)
例题
此部分参考了这篇文章
求 ∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = 1 ] , n ⩽ 1 e 7 \sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=1],n\leqslant1e7 ∑i=1n∑j=1m[gcd(i,j)=1],n⩽1e7 ;
由于 ∑ d ∣ n μ ( d ) = [ n = 1 ] \sum_{d|n}\mu(d)=[n=1] ∑d∣nμ(d)=[n=1] ,将 n 换为 g c d ( i , j ) gcd(i,j) gcd(i,j) ,即有 ∑ d ∣ g c d ( i , j ) μ ( d ) = [ g c d ( i , j ) = 1 ] \sum_{d|gcd(i,j)}\mu(d)=[gcd(i,j)=1] ∑d∣gcd(i,j)μ(d)=[gcd(i,j)=1] ;
将这个结论代入题给式,即为
∑
i
=
1
n
∑
j
=
1
m
∑
d
∣
g
c
d
(
i
,
j
)
μ
(
d
)
\sum_{i=1}^n\sum_{j=1}^m\sum_{d|gcd(i,j)}\mu(d)
i=1∑nj=1∑md∣gcd(i,j)∑μ(d)
在
[
1
,
n
]
[1,n]
[1,n] 区间中,有
d
,
2
d
,
3
d
,
.
.
.
,
⌊
n
d
⌋
d
d,2d,3d,...,\lfloor\frac n d\rfloor d
d,2d,3d,...,⌊dn⌋d ,共
⌊
n
d
⌋
\lfloor\frac n d\rfloor
⌊dn⌋ 个 d 的倍数;
在
[
1
,
m
]
[1,m]
[1,m] 区间中,有
d
,
2
d
,
3
d
,
.
.
.
,
⌊
m
d
⌋
d
d,2d,3d,...,\lfloor\frac m d\rfloor d
d,2d,3d,...,⌊dm⌋d ,共
⌊
m
d
⌋
\lfloor\frac m d\rfloor
⌊dm⌋ 个 d 的倍数;
两个集合中的 d 的整数倍数,两两的最大公约数均为 d 或 d 的倍数;
显然
d
∈
[
1
,
m
i
n
(
n
,
m
)
]
d\in[1,min(n,m)]
d∈[1,min(n,m)] ;
所以上式有以下变形
∑
d
=
1
m
i
n
(
n
,
m
)
μ
(
d
)
⌊
n
d
⌋
⌊
m
d
⌋
\sum_{d=1}^{min(n,m)}\mu(d)\lfloor\frac n d\rfloor\lfloor\frac m d\rfloor
d=1∑min(n,m)μ(d)⌊dn⌋⌊dm⌋
然后就可以通过整除分块优化对 d 的枚举了;
鸽巢原理
N 只鸽子放在 N-1 个鸽巢中的话,必存在多只鸽子在一个巢;
应用
例如:
f ( 1 ) = 1 , f ( 2 ) = 2 , f ( n ) = ( A f ( n − 1 ) + B f ( n − 2 ) ) % 7 f(1)=1,f(2)=2,f(n)=(Af(n-1)+Bf(n-2))\%7 f(1)=1,f(2)=2,f(n)=(Af(n−1)+Bf(n−2))%7 ,给定 n ,求 f ( n ) f(n) f(n)
这个问题中,如果存在 x , y x,y x,y 满足 x > y x>y x>y 且 { f ( y − 1 ) , f ( y ) } = { f ( x − 1 ) , f ( x ) } \{f(y-1),f(y)\}=\{f(x-1),f(x)\} {f(y−1),f(y)}={f(x−1),f(x)} ,这个数列即出现了循环;
关于循环节长度, { f ( x − 1 ) , f ( x ) } \{f(x-1),f(x)\} {f(x−1),f(x)} 一共有 7 2 = 49 7^2=49 72=49 种情况,即50项以内必出循环;
推广来说,对于需要前 n 项的递推方程,结果模 m ,那么循环节长度至多为 n ⋅ m n\cdotp m n⋅m ;
二进制操作
bitcount
返回给定数字中有多少二进制1;
O ( 1 ) O(1) O(1),参考与原理解释
int bitcount(int i)
{
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
i = (i + (i >> 4)) & 0x0f0f0f0f;
i = i + (i >> 8);
i = i + (i >> 16);
return i & 0x3f;
}
O ( l o g ( n ) ) O(log(n)) O(log(n))
int bitcount(int x){
return x==0?0:bitcount(x/2)+(x&1);
}
lowbit
返回给定数字中最低位1代表的十进制数;
一般用在树状数组中;
int lowbit(int x){return x&-x;}
ED
\