本人第一次写博客,欢迎提出其中的问题。
大佬们请跳过。
本人手懒,尽量少打点字
快速幂
再说快速幂之前,先说说普通的幂
幂
问题: 求 a n a^n an
一般的 ,遇到这样的问题,我们通常会用一个for
循环,来算出
a
∗
a
∗
.
.
.
∗
a
(
n
个
a
)
a * a * ...*a(n个a)
a∗a∗...∗a(n个a)的答案,最后快乐 解决问题。
如此算下来,时间复杂度为 O ( n ) O(n) O(n)。
如:
int pow(int a,int n){
int ans = 0;
for(int i = 1 ; i <= n ; i++ )
ans *= a;
return a;
}
然而,当
a
a
a,
n
n
n 过大时,这种方法就不太适用了。于是,快速幂
诞生了。
快速幂
思路
快速幂(很快),是运用了二分
的思想。我们来记
b
=
n
/
2
b = n / 2
b=n/2。当
n
n
n 为偶数时,
a
n
=
a
b
∗
a
b
a^n=a^b*a^b
an=ab∗ab,当
n
n
n 为奇数时,
a
n
=
a
b
∗
a
b
∗
a
a^n=a^b*a^b*a
an=ab∗ab∗a,这样,原问题被我们转化成了形式相同的子问题的乘积。时间复杂度
大大减少,变成了
O
(
l
o
g
n
)
O(logn)
O(logn)。
代码实现
问题模板:洛谷P1226 【模板】快速幂||取余运算
首先我们可以直接按照上述递归方法实现:
typedef long long ll;
ll q_pow(ll a,ll k) {
if(k == 1)
return a;
ll res = qpow1(a,k/2);
res = res * res;
if(k & 1)
res *= a;
return res;
}
当然,还有一种实现方法是非递归式的:
typedef long long ll;
ll qpow(ll a,ll k) {
ll res = 1;
while (k){
if(k & 1)
res = res * a ;
k >> 1;
a = a * a ;
}
return res;
}
实际应用
一般地,问题都不会这么简单,基本都是:
问题: 计算 a k a^k ak m o d mod mod m m m
当然,这也是一个十分常见的应用。
例如,它可以用于计算模意义下的乘法逆元
。
而且,我们知道取模的运算不会干涉乘法运算,因此我们只需要在计算的过程中快乐地取模
。
递归代码:
typedef long long ll;
ll q_pow(ll a,ll k,ll mod) {
a %= mod;
if(k == 1)
return a;
ll res = qpow1(a,k/2);
res = res * res % mod;
if(k & 1)
res = res * a % mod;
return res % mod;
}
非递归代码:
typedef long long ll;
ll qpow(ll a,ll k,ll mod) {
ll res = 1;
while (k){
if(k & 1)
res = res * a ;
k >> 1;
a = a * a ;
}
return res;
}
素数(质数)
如果一个大于
1
1
1 的正整数
a
a
a 可以被
1
1
1 和
a
a
a 整除,如果除此之外
a
a
a 没有其他的约数,则称
a
a
a 是素数。(小学生都知道的定义 )
素数判定
我们如何用计算机来判断一个数是不是素数呢?
问题:判断 a a a 是否是素数。
我们一般会用暴力做法,枚举从 1 1 1 ~ a a a 的每个数,看看是否把 a a a 整除。
一个半模板题:洛谷:AT1476 素数判定
代码如下:
bool zs(int n){
for(int i = 2 ; i < n ; i++)
if(n % i == 0)
return false;
return true;
}
但是,我们通过研究发现:不用每个数都判断。
如果一个数 a a a 能被 b b b 整除,则 a a a 一定也能被 a / b a/b a/b 整除 。所以,我们判 b b b 的同时,也把 a / b a/b a/b 给判了。
优化后代码:
bool zs(int n){
if (a < 2) return 0;
for(int i = 2 ; i * i <= n ; i++)
if(n % i == 0)
return false;
return true;
}
素数筛法
如果我们想要知道小于等于 n n n 有多少个素数呢?
一般地,我们对于小于等于 n n n 的每个数进行一次质数检验。但是,这种暴力的做法显然不能达到最优的复杂度。
埃拉托斯特尼筛法
埃拉托斯特尼筛法,简称埃氏筛或爱氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。
模板:洛谷:P5736 【深基7.例2】质数筛
思路
我们要得到自然数 n n n 以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。
如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。
比如给出要筛数值的范围 n n n,找出以内的素数。先用 2 2 2去筛,即把 2 2 2留下,把2的倍数剔除掉;再用下一个质数,也就是 3 3 3筛,把 3 3 3留下,把 3 3 3的倍数剔除掉;接下去用下一个质数 5 5 5筛,把 5 5 5留下,把 5 5 5的倍数剔除掉…以此类推,不断重复下去。
代码如下:
bool b[10010];
int zs[10010],cnt=0;
int ass(int n){
for(int i = 2 ; i <= n ; i++){
if(!b[i]){
zs[++cnt] = i;
for(int j = i * i; j <= n; j += i)
b[i * j] = true;
}
}
return cnt;
}
线性筛
埃氏筛法会将一个合数多次重复标记。
有没有什么办法省掉无意义的步骤呢?答案是肯定的。
线性筛法就让每一个合数都只被标记一次。那么时间复杂度就可以降到 O ( n ) O(n) O(n) 了。
模板:洛谷:P3383 【模板】线性筛素数
代码如下:
const int N=1e8+10;
bool b[N];
int a[N],cnt=0;
void q1(int n){
for(int i = 2;i <= n ;i++){
if(!b[i]){
cnt++;
a[cnt]=i;
}
for(int j = 1 ; a[j] <= n/i ; j++){
b[i * a[j]] = true;
if(i % a[j] == 0)
break;
}
}
}
if(i % a[j] == 0) break;
这个判断语句其实就是判断a[j]
是否在i中如果在i中证明i中已经包含了接下来的数的最小质因子(即为a[]
) , 但是继续往下循环a[j]
会不断变大,那么就没有循环的必要了,所以就要退出循环。所以这个代码几乎每个数只跑过一遍,时间复杂度为:
O
(
n
)
O(n)
O(n)