1.试除法求约数 O(√n)
一个数的约数一定是成对出现的,所以只要判断较小的约数,枚举到 d<√n即可
void get_divisors(int n)
{
vector<int> v;
for(int i=1;i<=n/i;i++)
{
if(n%i==0)
{
v.push_back(i);
if(i!=n/i)//把更大的那个约数加进来
v.push_back(n/i);
}
}
sort(v.begin(),v.end());
for(int i=0;i<v.size();i++)
cout<<v[i]<<" ";
cout<<endl;
}
时间复杂度分析:O(√n+mlogm)
,m是约数个数,上述代码只会循环到√n,实际上约数的个数很小,大概logn
个,所以排序的时间基本可以忽略。
约数个数:1 ~ n 中,每个数都是它倍数的约数,1 ~ n 中总共约数的个数等于总共倍数的个数
所以 对1来说它有n个倍数,对2来说它有n/2个倍数,…,对n来说它有1个倍数,所以约数总个数为n*lnn=>nlogn,所以平均来看,对每个数来说,它的约数个数就为logn
所以排序的时间复杂度为logn loglogn(代入nlogn)
,所以后面半部分其实可以忽略。
做数论题目一定要注意每一步 计算时间复杂度,只有所有都不超才可以
2.约数个数
若整数 n除以整数 d的余数为 0,即 d能整除 n,则称 d是n的约数 ,n是d的倍数,记为 d|n 。
唯一分解定理(算数基本定理): 任一大于 1的 自然数 𝑁,都可以唯一分解为有限个素数之积: N = P 1 c 1 P 2 c 2 . . . P r c r N=P_{1}^{c_1}P_{2}^{c_2}...P_{r}^{c_r} N=P1c1P2c2...Prcr
N的正约数集合可写作: { p 1 b 1 p 2 b 2 . . . p m b m } \left\{p_{1}^{b_1}p_{2}^{b_2}...p_{m}^{b_m}\right\} {p1b1p2b2...pmbm},其中 0 ⩽ b i ⩽ c i 0\leqslant b_i\leqslant c_i 0⩽bi⩽ci
N的正约数个数为
(
c
1
+
1
)
∗
(
c
2
+
1
)
∗
.
.
.
∗
(
c
m
+
1
)
=
Π
i
=
1
m
(
c
i
+
1
)
\left( c_1+1 \right) *\left( c_2+1 \right)*...*\left( c_m+1 \right) =\varPi_{i=1}^{m}\left( c_i+1 \right)
(c1+1)∗(c2+1)∗...∗(cm+1)=Πi=1m(ci+1) 还要包括0,即不选这个质因子的情况,所以是0~c_i种选法,数量为c_i+1,再通过乘法原理可得总个数
在int范围内,约数个数最多的数,最多有1500个左右约数
例题
输入
3
2
6
8
输出
12
思路
把每个
a
i
a_i
ai都分解质因数,将质因数和它的指数建立映射关系,可以用哈希表或map存下来,这样后续的就只需要增加指数即可。
注意:
这里可能会有2*109个数,开全局数组最多只能106或107左右,所以这里没办法用数组来存,我们选用unordered_map
来存。
非频繁的查询用map比较稳定;频繁的查询用hash_map效率会高一些,c++11中的unordered_map查询效率会更高一些但是内存占用比hash_map稍微大点。unordered_map 就是 boost 里面的 hash_map 实现。
优点:
因为内部实现了哈希表,因此其查找速度非常的快
缺点:
哈希表的建立比较耗费时间
STL——map/unordered_map基础用法
unordered_map的使用
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
const int maxn=105;
const int mod=1e9+7;
using namespace std;
int main()
{
int n;
cin>>n;
unordered_map<int,int> primes;
while(n--)
{
int x;
cin>>x;
for(int i=2;i<=x/i;i++)
{
while(x%i==0)
{
x/=i;
primes[i]++;
}
}
if(x>1)
primes[x]++;
}
long long ans=1;
unordered_map<int,int>::iterator it;
for(it=primes.begin();it!=primes.end();it++)
ans=ans*(it->second+1)%mod;//这里是先乘再取模,不能写作ans*=(it->second+1)%mod,因为这样会先取模再乘到ans上
cout<<ans<<endl;
return 0;
}
注意:ans可能会溢出(ans
取模后会在int范围内,但ans*it->second
不一定),要用long long
来存,long long 大约9*1018
3.约数之和
N的所有正约数之和为:
(
1
+
p
1
+
p
1
2
+
.
.
.
+
p
1
c
1
)
∗
(
1
+
p
2
+
p
2
2
+
.
.
.
+
p
2
c
2
)
∗
.
.
.
∗
(
1
+
p
m
+
p
m
2
+
.
.
.
+
p
m
c
m
)
=
Π
m
i
=
1
(
Σ
j
=
0
c
i
(
p
i
)
j
)
\left( 1+p_1+p_{1}^{2}+...+p_{1}^{c_1}\right) *\left( 1+p_2+p_{2}^{2}+...+p_{2}^{c_2} \right) *...*\left(1+p_m+p_{m}^{2}+...+p_{m}^{c_m} \right)=\underset{i=1}{\overset{m}{\varPi}}\left(\varSigma _{j=0}^{c_i}\left( p_i\right) ^j \right)
(1+p1+p12+...+p1c1)∗(1+p2+p22+...+p2c2)∗...∗(1+pm+pm2+...+pmcm)=i=1Πm(Σj=0ci(pi)j) 1是
p
i
0
p_i^0
pi0
乘法分配律展开之后,
=
(
A
)
+
(
B
)
+
(
C
)
+
.
.
.
+
=(A)+(B)+(C)+...+
=(A)+(B)+(C)+...+,其中A=在上式每一个括号中取一个相乘,取法有
C
c
1
+
1
1
∗
C
c
2
+
1
1
∗
C
c
3
+
1
1
+
.
.
.
+
C
c
m
+
1
1
C_{c_1+1}^1*C_{c_2+1}^1*C_{c_3+1}^1+...+C_{c_m+1}^1
Cc1+11∗Cc2+11∗Cc3+11+...+Ccm+11种(就是上式约数个数公式)。
接上例
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
const int maxn=105;
const int mod=1e9+7;
using namespace std;
int main()
{
int n;
cin>>n;
unordered_map<int,int> primes;
while(n--)
{
int x;
cin>>x;
for(int i=2;i<=x/i;i++)
{
while(x%i==0)
{
x/=i;
primes[i]++;
}
}
if(x>1)
primes[x]++;
}
long long ans=1;
unordered_map<int,int>::iterator it;
for(it=primes.begin();it!=primes.end();it++)
{
long long p=it->first,c=it->second;
long long t=1;
while(c--)//次数一直减1
{
t=(t*p+1)%mod;
}
ans=ans*t%mod;
}
cout<<ans<<endl;
return 0;
}
t=1;
t=p*t+1;
t=p*(p*t+1)+1=p^2*t+p+1
...
t=p^c+...+1
这里求和可以不用快速幂,快速幂的时间复杂度为log(k)
k是指数,如果有n项,时间复杂度的为O(nlogn),而以上求法为O(n)的,长度如果为n就只需要求n次,如果用分治去求的话,可以到log c,c为指数。
4. 求最大公约数 欧几里得算法(辗转相除法) O(logn)
核心原理:d|a,d|b,d|(ax+by) (a,b)=(b,a%b)
a
%
b
=
a
−
⌊
a
b
⌋
∗
b
,
(
a
,
b
)
=
(
b
,
a
−
c
∗
b
)
=
(
b
,
a
−
c
∗
b
+
c
∗
b
)
=
(
b
,
a
)
a\%b=a-⌊\frac{a}{b}⌋*b,(a,b)=(b,a-c*b)=(b,a-c*b+c*b)=(b,a)
a%b=a−⌊ba⌋∗b,(a,b)=(b,a−c∗b)=(b,a−c∗b+c∗b)=(b,a)
d
∣
a
,
d
∣
b
,
d
∣
a
−
c
∗
b
,
d
∣
a
−
c
∗
b
+
c
∗
b
d|a,d|b,d|a-c*b,d|a-c*b+c*b
d∣a,d∣b,d∣a−c∗b,d∣a−c∗b+c∗b
递归结束条件:(a,0)=a
(0%x=0)
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;//b==0时,最大公约数是a
}