文章目录
前提知识复习
整除分块是用于快速处理形似下列式子的方法,是解决莫比乌斯反演类题目需要掌握的前提知识
∑
i
=
1
n
⌊
n
i
⌋
\sum_{i=1}^n\lfloor\frac{n}{i}\rfloor
i=1∑n⌊in⌋
但是本篇博客的例题是特别特别板的,不会涉及莫比乌斯反演,请dalao们出门左转别浪费时间,蟹蟹
回归正题,很显然上面的式子可以
O
(
n
)
O(n)
O(n)得到答案
但是,在某些题目中,毒瘤出题人将数据加强到了
1
0
10
10^{10}
1010以上
这个时候我们就无法通过
O
(
n
)
O(n)
O(n) 的解法来得到答案
我们需要一个
O
(
n
)
O(\sqrt{n})
O(n)的更为优秀的解法
对于单一的
⌊
n
i
⌋
\lfloor\frac{n}{i}\rfloor
⌊in⌋,某些地方的值是相同的,并且呈块状分布
通过深入的探求规律与严密推理以及暴力打表与幸运瞎猜 ,最后惊奇的发现这些块状分布的值是有规律的
对于一个块,假设它的起始位置的下标为
l
l
l,那么可以得到的是,它的结束位置的下标为
代码表示即为
r = n / ( n / l );
分块如果非要安排一个模板的话,那就是一个
f
o
r
for
for循环了
int calc( int n ) {
int ans = 0;
for( int l = 1, r;l <= n;l = r + 1 ) {
r = n / ( n / l );
//根据题目要求进行计算
}
return ans;
}
有的时候不同的题目可能出现 n / l = = 0 n/l==0 n/l==0的情况,为了防止程序挂掉,我们也可以这么写
int calc( int n ) {
int ans = 0;
for( int l = 1, r;l <= n;l = r + 1 ) {
if( k / l ) r = min( k / ( k / l ), n );
else r = n;
//根据题目要求进行计算
}
return ans;
}
具体的就用例题来体会吧
T1:余数求和
title
solution
G
(
n
,
k
)
=
∑
i
=
1
n
k
m
o
d
i
=
∑
i
=
1
n
(
k
−
⌊
k
i
⌋
∗
i
)
=
∑
i
=
1
n
k
−
∑
i
=
1
n
⌊
k
i
⌋
∗
i
G(n,k)= ∑_{i=1}^n k\ mod\ i=\sum_{i=1}^n(k-\lfloor\frac{k}{i}\rfloor*i)=\sum_{i=1}^nk-\sum_{i=1}^n\lfloor\frac{k}{i}\rfloor*i
G(n,k)=i=1∑nk mod i=i=1∑n(k−⌊ik⌋∗i)=i=1∑nk−i=1∑n⌊ik⌋∗i
前面的求和式子可以很直观地得到
∑
i
=
1
n
k
=
n
∗
k
\sum_{i=1}^nk=n*k
∑i=1nk=n∗k
后面的求和式子我们令
l
l
l表示这个块的开始下标,
r
r
r为这个块的结束下标,
T
=
⌊
n
i
⌋
T=\lfloor\frac{n}{i}\rfloor
T=⌊in⌋,则该块里面的值为:
∑
i
=
l
r
T
∗
i
=
∑
i
=
l
r
T
−
∑
i
=
l
r
i
\sum_{i=l}^rT*i=\sum_{i=l}^rT-\sum_{i=l}^ri
i=l∑rT∗i=i=l∑rT−i=l∑ri
答案显而易见了吧,第一个求和就是个定值
(
r
−
l
+
1
)
∗
T
(r-l+1)*T
(r−l+1)∗T,后面的求和就是等差数列
计算方法:首项加末项乘以项数除以二=> ( l + r ) ∗ ( r − l + 1 ) / 2 (l+r)*(r-l+1)/2 (l+r)∗(r−l+1)/2
code
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
signed main() {
int n, k;
scanf( "%lld %lld", &n, &k );
int ans = n * k;
for( int l = 1, r;l <= n;l = r + 1 ) {
if( k / l ) r = min ( k / ( k / l ), n );
else r = n;
ans -= ( r - l + 1 ) * ( k / l ) * ( l + r ) / 2;
}
printf( "%lld", ans );
return 0;
}
T2:Ice Rain
title
solution
读完题后是不是
一模一样的吧,这种sb题 你加一个无线输入操作就
A
C
AC
AC,
p
a
s
s
pass
pass,下一个!!
code
#include <cstdio>
#include <iostream>
using namespace std;
int main() {
long long n, k;
while( ~ scanf( "%lld %lld", &n, &k ) ) {
long long ans = n * k;
for( long long l = 1, r;l <= n;l = r + 1 ) {
if( k / l ) r = min( k / ( k / l ), n );
else r = n;
ans -= ( k / l ) * ( l + r ) * ( r - l + 1 ) / 2;
}
printf( "%lld\n", ans );
}
return 0;
}
T3:The Fool
title
solution
一样的吧?差不多的吧?如果你没有一点思路的话,证明我写的太差 没有学懂哦~
∑
i
=
1
n
⌊
n
i
⌋
∑_{i=1}^n \lfloor\frac{n}{i}\rfloor
∑i=1n⌊in⌋,先分出每个块,然后再等差数列求和,加在一起最后判断,
v
a
n
van
van事
code
#include <cstdio>
int main() {
int T;
scanf( "%d", &T );
for( int Case = 1;Case <= T;Case ++ ) {
long long n;
scanf( "%lld", &n );
long long ans = 0;
for( int l = 1, r;l <= n;l = r + 1 ) {
r = n / ( n / l );
ans += ( r - l + 1 ) * ( n / l );
}
if( ans & 1 ) printf( "Case %d: odd\n", Case );
else printf( "Case %d: even\n", Case );
}
return 0;
}
T4:模积和
title
solution
这道题就是个重头戏了,其实也很简单的
仔细看推导过程!!
a
n
s
=
∑
i
=
1
n
∑
j
=
1
m
(
n
m
o
d
i
)
×
(
m
m
o
d
j
)
,
i
≠
j
ans=∑_{i=1}^n∑_{j=1}^m(n\ mod\ i)×(m\ mod\ j),i≠j
ans=i=1∑nj=1∑m(n mod i)×(m mod j),i=j
用容斥拆开把
i
=
j
i=j
i=j的情况减掉即可
a
n
s
=
∑
i
=
1
n
∑
j
=
1
m
(
n
m
o
d
i
)
×
(
m
m
o
d
j
)
−
∑
i
=
1
m
i
n
(
n
,
m
)
(
n
m
o
d
i
)
×
(
m
m
o
d
i
)
ans=∑_{i=1}^n∑_{j=1}^m(n\ mod\ i)×(m\ mod\ j)-∑_{i=1}^{min(n,m)}(n\ mod\ i)×(m\ mod\ i)
ans=i=1∑nj=1∑m(n mod i)×(m mod j)−i=1∑min(n,m)(n mod i)×(m mod i)
直接把显而易见能分块的先分了来,再暴力展开
∑
i
=
1
n
(
n
m
o
d
i
)
=
∑
i
=
1
n
n
−
∑
i
=
1
n
⌊
n
i
⌋
∗
i
∑_{i=1}^n(n\ mod\ i)=\sum_{i=1}^nn-\sum_{i=1}^n\lfloor\frac{n}{i}\rfloor*i
i=1∑n(n mod i)=i=1∑nn−i=1∑n⌊in⌋∗i
∑
j
=
1
m
(
m
m
o
d
j
)
=
∑
j
=
1
m
m
−
∑
j
=
1
m
⌊
m
j
⌋
∗
j
∑_{j=1}^m(m\ mod\ j)=\sum_{j=1}^mm-\sum_{j=1}^m\lfloor\frac{m}{j}\rfloor*j
j=1∑m(m mod j)=j=1∑mm−j=1∑m⌊jm⌋∗j
∑
i
=
1
m
i
n
(
n
,
m
)
(
n
m
o
d
i
)
×
(
m
m
o
d
i
)
=
∑
i
=
1
m
i
n
(
n
,
m
)
(
n
−
⌊
n
i
⌋
∗
i
)
∗
(
m
−
⌊
m
i
⌋
∗
i
)
∑_{i=1}^{min(n,m)}(n\ mod\ i)×(m\ mod\ i)=\sum_{i=1}^{min(n,m)}(n-\lfloor\frac{n}{i}\rfloor*i)*(m-\lfloor\frac{m}{i}\rfloor*i)
i=1∑min(n,m)(n mod i)×(m mod i)=i=1∑min(n,m)(n−⌊in⌋∗i)∗(m−⌊im⌋∗i)
=
∑
i
=
1
m
i
n
(
n
,
m
)
(
n
m
+
⌊
m
i
⌋
⌊
n
i
⌋
i
2
−
⌊
m
i
⌋
i
−
⌊
n
i
⌋
i
)
=\sum_{i=1}^{min(n,m)}(nm+\lfloor\frac{m}{i}\rfloor\lfloor\frac{n}{i}\rfloor i^2-\lfloor\frac{m}{i}\rfloor i-\lfloor\frac{n}{i}\rfloor i)
=i=1∑min(n,m)(nm+⌊im⌋⌊in⌋i2−⌊im⌋i−⌊in⌋i)
整理综上
a
n
s
=
(
∑
i
=
1
n
n
−
∑
i
=
1
n
⌊
n
i
⌋
∗
i
)
(
∑
i
=
1
m
m
−
∑
i
=
1
m
⌊
m
i
⌋
∗
i
)
−
∑
i
=
1
m
i
n
(
n
,
m
)
(
n
m
+
⌊
m
i
⌋
⌊
n
i
⌋
i
2
−
⌊
m
i
⌋
i
−
⌊
n
i
⌋
i
)
ans=(\sum_{i=1}^nn-\sum_{i=1}^n\lfloor\frac{n}{i}\rfloor*i)(\sum_{i=1}^mm-\sum_{i=1}^m\lfloor\frac{m}{i}\rfloor*i)-\sum_{i=1}^{min(n,m)}(nm+\lfloor\frac{m}{i}\rfloor\lfloor\frac{n}{i}\rfloor i^2-\lfloor\frac{m}{i}\rfloor i-\lfloor\frac{n}{i}\rfloor i)
ans=(i=1∑nn−i=1∑n⌊in⌋∗i)(i=1∑mm−i=1∑m⌊im⌋∗i)−i=1∑min(n,m)(nm+⌊im⌋⌊in⌋i2−⌊im⌋i−⌊in⌋i)
最后我们发现
i
2
i^2
i2在分块的时候挂掉了,OMG,怎么办!!,告诉你一个结论
∑ i n i 2 = n ∗ ( n + 1 ) ∗ ( 2 n + 1 ) / 2 ∑_i^ni^2=n∗(n+1)∗(2n+1)/2 i∑ni2=n∗(n+1)∗(2n+1)/2
最后注意避雷!!!
p
p
p不是个质数,稍微用随便找个互质的数搞个逆元就好了
code
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define mod 19940417
int n, m, inv;
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
int sqr( int x ) {
return x * ( x + 1 ) % mod * ( x << 1 | 1 ) % mod * inv % mod;
}
int sum( int l, int r ) {
return ( l + r ) * ( r - l + 1 ) / 2 % mod;
}
int calc( int n ) {
int ans = 0;
for( int l = 1, r;l <= n;l = r + 1 ) {
r = n / ( n / l );
ans = ( ans + n * ( r - l + 1 ) % mod - ( n / l ) * sum( l, r ) % mod + mod ) % mod;
}
return ans;
}
signed main() {
inv = qkpow( 6, 17091779 );
scanf( "%lld %lld", &n, &m );
int ans = calc( n ) * calc( m ) % mod;
if( n > m ) swap( n, m );
for( int l = 1, r;l <= n;l = r + 1 ) {
r = min( n / ( n / l ), m / ( m / l ) );
int sum1 = n * m % mod * ( r - l + 1 ) % mod;
int sum2 = ( n / l ) * ( m / l ) % mod * ( sqr( r ) - sqr( l - 1 ) + mod ) % mod;
int sum3 = ( n / l * m % mod + m / l * n % mod ) * sum( l, r ) % mod;
ans = ( ans - ( sum1 + sum2 - sum3 + mod ) % mod + mod ) % mod;
}
printf( "%lld", ans );
return 0;
}
走了,题还没做完…