质数判定
单个素数判定
bool is_prime(ll x)
{
for(int i=2;i*i<=x;++i)
if(x%i==0)return false;
return true;
}
多个素数预处理
const int maxn = 1e6 + 10;
bool vis[maxn];
int pri[maxn/5],tot;
void init()
{
for(int i=2;i<=n;i++){
if(!vis[i])pri[++tot]=i;
for(int j=1;i*pri[j]<=n&&j<=tot;j++){
vis[i*pri[j]]=1;
if(i%pri[j]==0)break;
//欧拉筛的精髓就在于break;
}
}
}
为什么需要break呢,或者说为什么这个时间段需要break.
解决这个问题之前需要知道,欧拉筛是利用什么来加速筛选的。(每个合数都会被它的最小质因子筛去)
若 i % pri[j] ==0
,则 i存在因子pri
[j],又pri[j]
递增,因此后面所有的pri[j]
将不能作为最小质因子筛去i*pri[j]
。
题外话
很多题目要求判断1e9内的相邻素数,不能使用素数筛法
经测试,1e9内相邻素数的最大距离是282,暴力判断就行,通过这一点衍生出很多结论,莽就完事了
数论分块
很多时候可以见到这类的式子
∑
i
=
1
n
⌊
n
i
⌋
\sum_{i=1}^n{\lfloor \frac{n}{i} \rfloor}
i=1∑n⌊in⌋
朴素的想法是for循环,然而只能处理2e7.
我们试着打一下表格
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | |||||||||||||||||
2 | 2 | 1 | ||||||||||||||||
3 | 3 | 1 | 1 | |||||||||||||||
4 | 4 | 2 | 1 | 1 | ||||||||||||||
5 | 5 | 2 | 1 | 1 | 1 | |||||||||||||
6 | 6 | 3 | 2 | 1 | 1 | 1 | ||||||||||||
7 | 7 | 3 | 2 | 1 | 1 | 1 | 1 | |||||||||||
8 | 8 | 4 | 2 | 2 | 1 | 1 | 1 | 1 | ||||||||||
9 | 9 | 4 | 3 | 2 | 1 | 1 | 1 | 1 | 1 | |||||||||
10 | 10 | 5 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | ||||||||
11 | 11 | 5 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | |||||||
12 | 12 | 6 | 4 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | ||||||
13 | 13 | 6 | 4 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |||||
14 | 14 | 7 | 4 | 3 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ||||
15 | 15 | 7 | 5 | 3 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |||
16 | 16 | 8 | 5 | 4 | 3 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ||
17 | 17 | 8 | 5 | 4 | 3 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
18 | 18 | 9 | 6 | 4 | 3 | 3 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
发现对于特定的N存在连续 的i
使得
⌊
N
i
⌋
\lfloor \frac{N}{i} \rfloor
⌊iN⌋值相等
如果我们可以知道连续的i
的起点和终点,那么就可以通过(r-l+1)*(N/l)
来得到区间的答案
我们不知道谁是起点,那么有没有可能对于任意的起点得到其终点呢。
对于任意的N
和i
都有
N
=
C
+
λ
∗
i
N = \ C + {\lambda*i}
N= C+λ∗i
此时
λ
=
⌊
N
i
⌋
\lambda = \lfloor \frac{N}{i} \rfloor
λ=⌊iN⌋
对于第一个式子,特定的 N
固定i
的取值后会得到最大的 “
λ
\lambda
λ”。同样的,若固定此时的“
λ
\lambda
λ”便能得到最大的i
。
也就是说已知i
时
R
=
N
/
λ
R = N/ \lambda
R=N/λ.同时
λ
=
⌊
N
i
⌋
\lambda = \lfloor \frac{N}{i} \rfloor
λ=⌊iN⌋.即
R
=
N
/
(
N
/
i
)
R= N/(N/i)
R=N/(N/i).
至此,我们已经知道了对于任意的起点获取其终点的方式.同时不难发现 新的起点就是上一个终点+1
.那么代码就已经呼之欲出了
ll solve(ll n)
{
ll ans=0;
for(ll l=1,r;l<=n;l=r+1){
r = n/(n/l);
ans += (r-l+1)*(n/l);
}
return ans;
}
好了现在你已经学会数论分块了.那么来做个练习题吧
BZOJ 1257
题面是 ∑ i N N m o d i \sum_i^N{\ N \space mod \space i} ∑iN N mod i
∑ i N N m o d i = ∑ i N N − ⌊ N i ⌋ ∗ i \sum_i^N{\ N \space mod \space i} = \sum_i^N{N- \lfloor \frac{N}{i} \rfloor * i} i∑N N mod i=i∑NN−⌊iN⌋∗i
即便不会也没有关系,继续往下学.
多关键字数论分块
这个样子
∑
i
m
i
n
(
N
,
M
)
⌊
N
i
⌋
⌊
M
i
⌋
\sum_i^{min(N,M)}{\lfloor\frac{N}{i}\rfloor\lfloor\frac{M}{i}\rfloor}
i∑min(N,M)⌊iN⌋⌊iM⌋
现在对于N,i
而言L~R1
区间的值都是
⌊
N
i
⌋
\lfloor \frac{N}{i} \rfloor
⌊iN⌋,对于M,i
而言L~R2
区间的值都是
⌊
M
i
⌋
\lfloor \frac{M}{i} \rfloor
⌊iM⌋,我们只需控制
C
=
⌊
N
i
⌋
⌊
M
i
⌋
\ C= {\lfloor\frac{N}{i}\rfloor\lfloor\frac{M}{i}\rfloor}
C=⌊iN⌋⌊iM⌋ .
C
不变时,那么求和范围就是两个区间的交集,也就是L~min(R1,R2)
.
那么答案就是(min(R1,R2) - l +1)*(N/l)*(M/l)
ll solve(int n,int m)
{
ll ans=0;
if(n>m)swap(n,m);
for(int l=1,r;l<=n;l=r+1){
r = min(n/(n/l),m/(m/l));
ans += (r-l+1)*(n/l)*(m/l);
}
}
数论分块应用
令
f
(
x
)
=
1
f(x) = 1
f(x)=1,则
∑
i
n
⌊
N
i
⌋
=
∑
i
n
⌊
N
i
⌋
∗
f
(
i
)
\sum_i^n\lfloor \frac{N}{i} \rfloor = \sum_i^n{\lfloor\frac{N}{i}\rfloor*f(i)}
i∑n⌊iN⌋=i∑n⌊iN⌋∗f(i)
如果我们先对
f
(
x
)
f(x)
f(x)函数预处理好前缀和,那么原答案的求法将会是这样子.
在这个求和计算中,可以将
⌊
N
i
⌋
\lfloor \frac{N}{i} \rfloor
⌊iN⌋理解为一个系数,而相当一段长度的1
是同一个系数.
c
=
⌊
N
i
⌋
i
∈
[
L
,
R
]
⌊
N
L
⌋
f
(
L
)
+
⌊
N
L
+
1
⌋
f
(
L
+
1
)
+
⌊
N
L
+
2
⌋
f
(
L
+
2
)
+
.
.
.
+
⌊
N
R
⌋
f
(
R
)
→
c
∗
(
f
(
L
)
+
f
(
L
+
1
)
+
f
(
L
+
2
)
+
.
.
.
+
f
(
R
)
)
=
c
∗
(
s
u
m
[
R
]
−
s
u
m
[
L
−
1
]
)
c = \lfloor \frac{N}{i} \rfloor \space i\in [L,R] \\ \lfloor \frac{N}{L} \rfloor f(L) + \lfloor \frac{N}{L+1} \rfloor f(L+1) +\lfloor \frac{N}{L+2} \rfloor f(L+2)+...+ \lfloor \frac{N}{R} \rfloor f(R) \\ \rightarrow c*(f(L)+f(L+1)+f(L+2)+...+f(R)) = c*(sum[R]-sum[L-1])
c=⌊iN⌋ i∈[L,R]⌊LN⌋f(L)+⌊L+1N⌋f(L+1)+⌊L+2N⌋f(L+2)+...+⌊RN⌋f(R)→c∗(f(L)+f(L+1)+f(L+2)+...+f(R))=c∗(sum[R]−sum[L−1])
const int maxn = 1e6+10;
int f[maxn];
void init()
{
for(int i=1;i<=n;i++){
f[i]=1;
f[i]+=f[i-1];
}
}
ll solve(ll n)
{
init();
ll ans=0;
for(ll l=1,r;l<=n;l=r+1){
r = n/(n/l);
ans += (f[r]-f[l-1])*(n/l);
}
return ans;
}
若给出
f
(
x
)
=
f
(
x
−
1
)
+
f
(
x
−
2
)
f(x)=f(x-1)+f(x-2)
f(x)=f(x−1)+f(x−2)呢?显然,需要更改的只是init()
函数其他的照写就行
因此我们发现,对于给定的任意式子形如 ∑ i n ⌊ N i ⌋ ∗ f ( i ) \sum_i^n\lfloor \frac{N}{i}\rfloor*f(i) ∑in⌊iN⌋∗f(i) 的式子只需要求出 f ( i ) f(i) f(i)后作前缀和后数论分块求解即可