质数
定义:在大于1的整数中,如果只包含1和本身两个约数,就被称为质数,或者称为素数。特别地,0,1既不是质数也不是合数。
质数的判定:
1. 质数判定——试除法
时间复杂度:
O
(
n
)
O(\sqrt{n})
O(n)
i从2枚举到n-1,如果n能被i整除,则为合数;如果都不能被i整除,则为质数;
如果n为合数,因子成对出现,记为(x1,x2),则必有
x
1
<
=
n
,
x
2
>
=
n
x1<=\sqrt{n},x2>=\sqrt{n}
x1<=n,x2>=n,所以i枚举到
n
\sqrt{n}
n即可
bool is_prime(int n)
{
if(n<2) return false;
else
{
for(int i=2;i<=n/i;i++)//i<sqrt(n) 每次会开方,计算量大 i*i<=n 有爆int风险
if(n%i==0) return false;
}
return true;
}
2. 分解质因数——试除法
时间复杂度:
O
(
log
2
n
)
O(\log_2 n)
O(log2n)~
O
(
n
)
O(\sqrt{n})
O(n)
n最多只有1个大于
n
\sqrt{n}
n的质因数
void divide(int n)
{
for(int i=2;i<n/i;i++)
{
if(n%i==0)
{
int s=0;
while(n%i==0) n/=i,s++;
cout<<i<<' '<<s<<endl;
}
}
if(n>1) cout<<n<<' '<<1<<endl;
}
3. 筛质数(求1-n中质数的数量)
1. 埃氏筛法(朴素筛)
时间复杂度:
O
(
n
l
n
n
)
O(nlnn)
O(nlnn)
对于每个i,内部循环次数
c
n
t
t
=
n
i
cnt_t=\frac{n}{i}
cntt=in
整个程序
c
n
t
=
∑
i
=
2
n
c
n
t
t
=
n
2
+
n
3
+
.
.
.
+
1
=
n
∑
i
=
2
n
1
i
≈
n
(
l
n
n
+
c
)
cnt=\sum_{i=2}^{n}{cnt_t}=\frac{n}{2}+\frac{n}{3}+...+1=n\sum_{i=2}^{n}{\frac{1}{i}}\approx n(lnn+c)
cnt=∑i=2ncntt=2n+3n+...+1=n∑i=2ni1≈n(lnn+c),c为欧拉常数
int prime[n],cnt=0;
bool st[n];
for(int i=2;i<n-1;i++)
{
if(!st[i]) //2~i-1都没有将i筛掉,所以i是质数
{
prime[cnt++]=i;
for(int j=i*2;j<=n;j+=i)
st[j]=true;
}
}
2. 线性筛法(欧拉筛)
1~n中大约有 n ln n \frac{n}{\ln{n}} lnnn个质数,时间复杂度大约是O(n),实际是O(nloglog(n))
int prime[n],cnt=0;
bool st[n];
for(int i=2;i<=n;i++)
{
if(!st[i]) prime[cnt++]=i;
for(int j=0;prime[j]<=n/i;j++)// 保证prime[j]*i<=n
{
st[prime[j]*i]=true;
// 如果i是质数,则prime[-1]=i 可以break
// 如果i是合数,则prime[j]=i的第一个质因子的时候break
if(i%prime[j]==0) break;
}
}
核心:每个合数只由它最小的质因子筛除
- i为质数
prime[j]从小到大遍历,prime[j]<i,prime[j]一定是prime[j]*i的最小质因子 - i为合数
设 i = x ∗ y i=x*y i=x∗y(x为i的最小质因子)
prime[j]从小到大遍历,当prime[j]<=x ,prime[j]一定是prime[j]*i的最小质因子
当prime[j]>x,x是prime[j]*i的最小质因子,所以prime[j]一旦遍历到x就终止遍历
例如:
i = 8 i=8 i=8(8的最小质因子为2)
16 = 2 ∗ 8 16=2*8 16=2∗8由i=8和最小质因子2筛除
24 = 3 ∗ 8 24=3*8 24=3∗8因为8中有质因子2,所以应该由i=12和质因子2筛除,不应该由i=8和质因子3筛除,所以当i遇到它的第一个质因子时应当退出循环。
约数
1.试除法求约数
时间复杂度:
O
(
n
)
O(\sqrt{n})
O(n)
i从1枚举到n,如果n能被i整除,则为i为n的一个约数;
因子成对出现,记为(x1,x2),则必有
x
1
<
=
n
,
x
2
>
=
n
x1<=\sqrt{n},x2>=\sqrt{n}
x1<=n,x2>=n,所以i枚举到
n
\sqrt{n}
n即可
vector<int> get(int n)
{
vector<int> res;
for(int i=1;i<=n/i;i++)
{
if(n%i==0)
{
res.push_back(i);
if(i!=n/i) res.push_back(n/i);
}
}
sort(res.begin(),res.end());
return res;
}
2.约数个数
AcWing 870. 约数个数
将x能表示成它所有质数的乘积
x
=
p
1
α
1
p
2
α
2
.
.
.
p
k
α
k
x=p_1^{\alpha1}p_2^{\alpha2}...p_k^{\alpha k}
x=p1α1p2α2...pkαk
那么它的任意一个约数可以表示成
d
=
p
1
β
1
p
2
β
2
.
.
.
p
k
β
k
d=p_1^{\beta1}p_2^{\beta2}...p_k^{\beta k}
d=p1β1p2β2...pkβk,
0
≤
β
i
≤
α
i
0\leq\beta i\leq\alpha i
0≤βi≤αi
所以n的约数总个数
n
=
(
α
1
+
1
)
(
α
2
+
1
)
.
.
.
(
α
k
+
1
)
n=(\alpha1+1)(\alpha2+1)...(\alpha k+1)
n=(α1+1)(α2+1)...(αk+1)
3.约数总和
AcWing 871. 约数之和
约数的个数中提到,数x的任意一个约数可以表示成
d
=
p
1
β
1
p
2
β
2
.
.
.
p
k
β
k
d=p_1^{\beta1}p_2^{\beta2}...p_k^{\beta k}
d=p1β1p2β2...pkβk,
0
≤
β
i
≤
α
i
0\leq\beta i\leq\alpha i
0≤βi≤αi,约数可以表示如下:
d
1
=
p
1
0
p
2
0
.
.
.
p
k
0
d1=p_1^0p_2^0...p_k^0
d1=p10p20...pk0
d
2
=
p
1
0
p
2
0
.
.
.
p
k
1
d2=p_1^0p_2^0...p_k^1
d2=p10p20...pk1
d
3
=
p
1
0
p
2
0
.
.
.
p
k
2
d3=p_1^0p_2^0...p_k^2
d3=p10p20...pk2
所有的约数就是
β
1
\beta1
β1~
β
k
\beta k
βk所有取值的全排列,即
(
p
1
0
+
p
1
1
+
.
.
.
+
p
1
α
1
)
(
p
2
0
+
p
2
1
+
.
.
.
+
p
2
α
2
)
.
.
.
(
p
k
0
+
p
k
1
+
.
.
.
+
p
k
α
k
)
(p_1^0+p_1^1+...+p_1^\alpha1)(p_2^0+p_2^1+...+p_2^\alpha2)...(p_k^0+p_k^1+...+p_k^{\alpha k})
(p10+p11+...+p1α1)(p20+p21+...+p2α2)...(pk0+pk1+...+pkαk)
4.最大公约数
a和b的最大公约数等于b和a%b的最大公约数
int get(int a,int b)
{
return b?get(b,a%b):a;
}