约数
定义
若整数n除以d的余数是0,即d能整除n,则称d是n的约数,n是d的倍数,记为d|n。
算法基本定理的推论
在算法基本定理中,若正整数N被唯一分解为
N
=
p
1
c
1
p
2
c
2
.
.
.
p
m
c
m
N=p_1^{c_1}p_2^{c_2}...p_m^{c_m}
N=p1c1p2c2...pmcm,其中ci都是正整数,pi都是质数,且满足
p
1
<
p
2
<
.
.
.
<
p
n
p_1<p_2<...<p_n
p1<p2<...<pn,则N的正约数集合可写作:
{
p
1
c
1
p
2
c
2
.
.
.
p
m
c
m
}
,其中
0
≤
b
i
≤
c
i
\{p_1^{c_1}p_2^{c_2}...p_m^{c_m}\},其中0 \leq b_i \leq c_i
{p1c1p2c2...pmcm},其中0≤bi≤ci
N的正约数个数是
(
c
1
+
1
)
(
c
2
+
1
)
∗
.
.
.
∗
(
c
m
+
1
)
=
∏
i
=
1
m
(
c
i
+
1
)
(c_1+1)(c_2+1)*...*(c_m+1)=\prod\limits_{i=1}^{m}(c_i+1)
(c1+1)(c2+1)∗...∗(cm+1)=i=1∏m(ci+1)
所有正约数的和为
(
1
+
p
1
+
p
1
2
+
.
.
.
+
p
1
c
1
)
∗
.
.
.
∗
(
1
+
p
m
+
p
m
2
+
.
.
.
+
p
m
c
m
)
=
∏
i
=
1
m
(
∑
i
=
1
c
i
(
p
i
)
j
)
(1+p_1+p_1^2+...+p_1^{c_1})*...*(1+p_m+p_m^2+...+p_m^{c_m})=\prod_{i=1}^m(\sum\limits_{i=1}^{c_i}(p_i)^j)
(1+p1+p12+...+p1c1)∗...∗(1+pm+pm2+...+pmcm)=i=1∏m(i=1∑ci(pi)j)
1.求N的正约数集合——试除法
若
d
≤
N
d \leq \sqrt{N}
d≤N是N的约数,则
N
/
d
≤
N
N/d \leq \sqrt{N}
N/d≤N也是N的约数。换言之,约数总是成对出现的。
因此只需要扫描
[
1
,
N
]
[1,\sqrt{N}]
[1,N]中的数,尝试d能否整除N,若能整除,则N/d也是N的约数,时间复杂度为
O
(
N
)
O(\sqrt{N})
O(N)。
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
N
2\sqrt{N}
2N。
2.求1-N每个数的正约数集合——被除法
若用试除法求1-N所有书的正约数集合,时间复杂度为
O
(
N
N
)
O(N\sqrt{N})
O(NN),可以反过来考虑,对于每个数d,1-N中以d为约数的数就是d的倍数d,2d,3d,…
⌊
N
/
d
⌋
∗
d
\lfloor N/d \rfloor*d
⌊N/d⌋∗d。
vector<int> factor[500010];
for (int i = 1; i <= n; i ++ )
for (int j = i; j <= n; j += i)
factor[j].push_back(i);
for (int i = 1; i <= n; i ++ )
{
for (int j = 0; j < factor[i].size(); j ++ )
printf("%d ",factor[i][j]);
put("");
}
时间复杂度 O ( N + N / 2 + N / 3 + . . . + N / N ) = O ( N l o g N ) O(N+N/2+N/3+...+N/N)=O(NlogN) O(N+N/2+N/3+...+N/N)=O(NlogN)。
倍除法的推论
1-N中每个数的约数个数总和大约是NlogN。
倍除法模板题,变动在于每个数的数量不一定是1。
#include <iostream>
using namespace std;
#define N 1000010
int a[N], cnt[N], sum[N];
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
cnt[a[i]] ++ ;
}
for (int i = 1; i < N; i ++ )
{
if (cnt[i] == 0) continue;
for (int j = i; j < N; j += i)
sum[j] += cnt[i];
}
for (int i = 1; i <= n; i ++ )
cout << sum[a[i]] - 1 << endl;
return 0;
}
1
x
+
1
y
=
1
n
!
\frac{1}{x}+\frac{1}{y}=\frac{1}{n!}
x1+y1=n!1
x
n
!
+
y
n
!
=
x
y
xn!+yn!=xy
xn!+yn!=xy
x
(
y
−
n
!
)
=
y
n
!
x(y-n!)=yn!
x(y−n!)=yn!
x
=
y
n
!
(
y
−
n
!
)
=
n
!
+
(
n
!
)
2
y
−
n
!
x=\frac{yn!}{(y-n!)}=n!+\frac{(n!)^2}{y-n!}
x=(y−n!)yn!=n!+y−n!(n!)2
又因为x和y一定大于n!,因此将本问题转化为对于x求整数解的个数,也即
(
n
!
)
2
y
−
n
!
\frac{(n!)^2}{y-n!}
y−n!(n!)2正整数解的个数,也即
(
n
!
)
2
(n!)^2
(n!)2约数的个数。
对于n!约数个数的求解可以采用分解质因数的方法,时间复杂度只有O(n), ( n ! ) 2 (n!)^2 (n!)2的约数个数求法同理。
#include <iostream>
using namespace std;
const int N = 1000010;
const int mod = 1e9 + 7;
typedef long long ll;
int prime[N];
bool st[N];
int n, cnt = 0;
void init(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) prime[cnt ++ ] = i;
for (ll j = 0; prime[j] * i <= n; j ++ )
{
st[i * prime[j]] = 1;
if (i % prime[j] == 0)break;
}
}
}
int main()
{
cin >> n;
ll res = 1;
init(n);
for(int i = 0; i < cnt; i ++ )
{
int p = prime[i];
ll s = 0;
for (ll j = p; j <= n; j *= p) s += n / j;
res = res * (2 * s + 1) % mod;
}
cout << res;
return 0;
}
acwing198.反素数
分析题目可以得到以下三个性质
1)不同的质因子个数最多有9个,即2,3,5,7,11,13,17,19,23,因为2*3*5*7*11*13*17*19*23*29大于了2e9,因此可能的质因子只有2到23。
2)约数的个数最多只有30,因为2^31刚好大于了2e9。
3)分解为
p
1
c
1
p
2
c
2
.
.
.
p
m
c
m
p_1^{c_1}p_2^{c_2}...p_m^{c_m}
p1c1p2c2...pmcm之后,一定有
c
1
≤
c
2
≤
.
.
.
≤
c
m
c_1 \leq c_2 \leq ... \leq c_m
c1≤c2≤...≤cm,因为如果出现
c
i
>
c
j
,
i
<
j
c_i >c_j,i <j
ci>cj,i<j,那么就可以通过交换ci和cj的值,得到约数个数相同但是值更小的数,使得原先的数不符合反素数的定义。
采用dfs来做,依次枚举每一个可能的约数的次数,后面的约数次数不能大于此约数的次数,一旦找到约数个数更多的数,或者约束个数相同但是值更小的数则更新答案。
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 2e9 + 7;
int prime[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
int n;
int max_cnt = 0, number = n;
//当前枚举到了第u个数,可以枚举的指数最大到last
//当前枚举到的数构成的数为mul,当前约数个数为cnt
void dfs(int u, int last, int mul, int cnt)
{
if (cnt > max_cnt || cnt == max_cnt && mul < number)
{
number = mul;
max_cnt = cnt;
}
if (u == 9)return ;
for (int i = 1; i <= last; i ++ )
{
if ((ll) mul * prime[u] > n) break;
mul *= prime[u];
dfs(u + 1, i, mul, cnt * (i + 1));
}
}
int main()
{
cin >> n;
dfs(0, 30, 1, 1);
cout << number;
return 0;
}
最大公约数
定义
若自然数d同时是自然数a和b的约数,则称d是a和b的公约数,在所有a和b的公约数里最大的一个,成为a和b的最大公约数,记为gcd(a, b)。
若自然数m同时是自然数a和b的倍数,则称m是a和b的公倍数,在所有a和b的公倍数里最小的一个,成为a和b的最小公倍数,记为lcm(a, b)。
定理
∀
a
,
b
∈
N
,
g
c
d
(
a
,
b
)
∗
l
c
m
(
a
,
b
)
=
a
∗
b
\forall a,b \in \mathbb{N}, gcd(a,b)*lcm(a,b)=a*b
∀a,b∈N,gcd(a,b)∗lcm(a,b)=a∗b
证明:
设
d
=
g
c
d
(
a
,
b
)
,
a
0
=
a
/
d
,
b
0
=
b
/
d
d=gcd(a,b),a0=a/d,b0=b/d
d=gcd(a,b),a0=a/d,b0=b/d。根据最大公约数的定义,又
g
c
d
(
a
0
,
b
0
)
=
1
gcd(a0,b0)=1
gcd(a0,b0)=1.再根据最小公倍数的定义,有
l
c
m
(
a
0
,
b
0
)
=
a
0
∗
b
0
lcm(a0,b0)=a0*b0
lcm(a0,b0)=a0∗b0。
于是
l
c
m
(
a
,
b
)
=
l
c
m
(
a
0
∗
d
,
b
0
∗
d
)
=
d
∗
l
c
m
(
a
0
,
b
0
)
=
a
0
∗
b
0
∗
d
=
a
∗
b
/
d
lcm(a,b)=lcm(a0*d,b0*d)=d*lcm(a0,b0)=a0*b0*d=a*b/d
lcm(a,b)=lcm(a0∗d,b0∗d)=d∗lcm(a0,b0)=a0∗b0∗d=a∗b/d。
证毕。
更相减损数
∀
a
,
b
∈
N
,
a
≥
b
,
有
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
−
b
)
=
g
c
d
(
a
,
a
−
b
)
\forall a,b \in \mathbb{N},a\geq b,有gcd(a,b)=gcd(b,a-b)=gcd(a,a-b)
∀a,b∈N,a≥b,有gcd(a,b)=gcd(b,a−b)=gcd(a,a−b)
∀
a
,
b
∈
N
,
有
g
c
d
(
2
a
,
2
b
)
=
2
g
c
d
(
a
,
b
)
\forall a,b \in \mathbb{N},有gcd(2a,2b)=2gcd(a,b)
∀a,b∈N,有gcd(2a,2b)=2gcd(a,b)
欧几里得算法
∀ a , b ∈ N , b ! = 0 , 有 g c d ( a , b ) = g c d ( b , a m o d b ) \forall a,b \in \mathbb{N},b!=0,有gcd(a,b)=gcd(b,a\ mod \ b) ∀a,b∈N,b!=0,有gcd(a,b)=gcd(b,a mod b)
证明:
若
a
<
b
,
则
g
c
d
(
b
,
a
m
o
d
b
)
=
g
c
d
(
b
,
a
)
=
g
c
d
(
a
,
b
)
,显然成立
a<b,则gcd(b,a\ mod \ b)=gcd(b,a)=gcd(a,b),显然成立
a<b,则gcd(b,a mod b)=gcd(b,a)=gcd(a,b),显然成立
若
a
≥
b
,
不妨设
a
=
q
∗
b
+
r
,
其中
0
≤
r
<
b
,
显然
r
=
a
m
o
d
b
,
对于
a
,
b
的任意公约数
d
,因为
d
∣
a
,
d
∣
q
∗
b
,
故
d
∣
(
a
−
q
∗
b
)
,
即
d
∣
r
,
因此
d
也是
b
,
r
的公约数。
a \geq b,不妨设a=q*b+r,其中0 \leq r < b,显然r=a \ mod \ b,对于a,b的任意公约数d,因为d|a,d|q*b,故d|(a-q*b),即d|r,因此d也是b,r的公约数。
a≥b,不妨设a=q∗b+r,其中0≤r<b,显然r=a mod b,对于a,b的任意公约数d,因为d∣a,d∣q∗b,故d∣(a−q∗b),即d∣r,因此d也是b,r的公约数。
故a,b的公约数集合和b,r的公约数集合相同,最大公约数自然也相同。
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
时间复杂度为 O ( l o g ( a + b ) ) O(log(a+b)) O(log(a+b))。
因为x和b0的最小公倍数是b1,所以x一定是b1的约数,所以可以考虑直接枚举b1的约数,并判断是否符合这两个条件,均满足的话则答案加1。
分析时间复杂度,首先两个判断的时间复杂度均为log(n)级别,对于每一个b1求约数的时间复杂度为
b
1
\sqrt{b1}
b1,结合已知结论,
1
0
9
以内的数最大的约数个数为
1536
10^9以内的数最大的约数个数为1536
109以内的数最大的约数个数为1536,因此总体时间复杂度为
O
(
T
∗
b
1
+
T
∗
1536
∗
l
o
g
n
)
O(T*\sqrt{b1}+T*1536*logn)
O(T∗b1+T∗1536∗logn),前半部分大大小为2000*5*10^4=10^8,会超时,考虑优化求约数的过程。
优化思路:
预处理出[1,50000]内的所有质数,质数个数大约为
n
/
l
n
n
\sqrt{n}/ln\sqrt{n}
n/lnn,然后对每一个数在
n
/
l
n
n
\sqrt{n}/ln\sqrt{n}
n/lnn的时间复杂度为分解质因数,分解之后可以通过一遍dfs枚举出所有的约数,dfs的时间复杂度约取决于数个数,不超过1536。
因此优化后的时间复杂度为
O
(
n
+
T
∗
(
n
/
l
n
n
+
1536
)
+
T
∗
1536
∗
l
o
g
n
)
。
O(\sqrt{n}+T*(\sqrt{n}/ln\sqrt{n}+1536)+T*1536*logn)。
O(n+T∗(n/lnn+1536)+T∗1536∗logn)。
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
#define N 500010
struct Data{
int p, c;
}factor[N];
int prime[N];
bool st[N];
int divider[N];
typedef long long ll;
//cnt 记录素数个数,cnt2记录一个数可以分解为的不同质因子个数
//cnt3记录一个数的约数个数
int n, cnt = 0, cnt2 = 0, cnt3 = 0;
int a0, a1, b0, b1;
void init(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i])prime[cnt ++ ] = i;
for (int j = 0; prime[j] * i <= n; j ++ )
{
st[i * prime[j]] = 1;
if (i % prime[j] == 0)break;
}
}
}
void divide(int x)
{
cnt2 = 0;
for (int i = 0; prime[i] <= sqrt(x); i ++ )
{
int p = prime[i];
if (x % p == 0)
{
int s = 0;
while (x % p == 0)
{
s += 1;
x /= p;
}
factor[cnt2 ++ ] = {p, s};
}
}
if(x > 1) factor[cnt2 ++ ] ={x, 1};
}
void dfs(int u, int p)
{
if (u == cnt2)
{
divider[cnt3 ++ ] = p;
return ;
}
for (int i = 0; i <= factor[u].c; i ++ )
{
dfs(u + 1, p);
p *= factor[u].p;
}
return ;
}
int gcd(int x, int y)
{
return y ? gcd(y, x % y) : x;
}
ll lcm(int x, int y)
{
return (ll) x * y / gcd(x, y);
}
int main()
{
init(N - 1);
cin >> n;
while(n -- )
{
cin >> a0 >> a1 >> b0 >> b1;
divide(b1);
cnt3 = 0;
dfs(0, 1);
int res = 0;
for (int i = 0; i < cnt3; i ++ )
{
if(gcd(a0, divider[i]) == a1 && lcm(b0, divider[i]) == b1)
res ++ ;
}
cout << res << endl;
}
return 0;
}