[SDOI2015]约数个数和
问题分析
首先有个很厉害的结论:
\[d(ij)=\sigma_0(ij)=\sum_{x|i}\sum_{y|j}[\gcd(x,y)=1]
\]
Prove:
\[\sigma_0(ij)=\sum\limits_{d|ij}1=\sum\limits_{d_1\mid i,d_2\mid j}1=\sum\limits_{d_1\mid i}\sum\limits_{d_2\mid j}[\gcd(\frac {i} {d_1},d_2)=1]
\]
解释:
我们拆分\(d=d_1d_2\),如果直接拆为\(\sum\limits_{d_1\mid i}\sum\limits_{d_2|j}1\),那么显然会记重。那么我们令\(\gcd(\frac{i}{d_1},d_2)=1\),也就是说让\(d_1\)塞的因数尽可能多,那么就不会记重了。而等式右边与原式右侧等价,即证。
然后就有一下操作:
\[Ans=\sum_{i=1}^N\sum_{j=1}^M\sum_{x|i}\sum_{y|j}[\gcd(x,y)=1]=\sum_{x=1}^N\sum_{y=1}^M[\gcd(x,y)=1]\lfloor\frac{N}{x}\rfloor\lfloor\frac{M}{y}\rfloor
\]
然后一波莫比乌斯反演:
\[f(t)=\sum_{x=1}^N\sum_{y=1}^M[\gcd(x,y)=1]\lfloor\frac{N}{x}\rfloor\lfloor\frac{M}{y}\rfloor\\
F(t)=\sum_{x=1}^N\sum_{y=1}^M[t|\gcd(x,y)]\lfloor\frac{N}{x}\rfloor\lfloor\frac{M}{y}\rfloor=\sum_{x=1}^{\lfloor\frac{N}{t}\rfloor}\sum_{y=1}^{\lfloor\frac{M}{t}\rfloor}\lfloor\frac{M}{tx}\rfloor\lfloor\frac{N}{ty}\rfloor=\sum_{t|d}f(d)\\
f(t)=\sum_{t|d}\mu(\frac{d}{t})F(d)
\]
所以
\[Ans=f(1)=\sum_{d}^{\min(N,M)}\mu(d)(\sum_{x=1}^{\lfloor\frac{N}{d}\rfloor}\lfloor\frac{N}{xd}\rfloor)(\sum_{y=1}^{\lfloor\frac{M}{d}\rfloor}\lfloor\frac{M}{yd}\rfloor)
\]
然后我们预处理\(\sum\limits_{x=1}\limits^{t}\lfloor\frac{t}{d}\rfloor\),然后整除分块正常操作就好了。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 50010;
void EulerSieve();
void Cal();
int main() {
EulerSieve();
int TestCases;
scanf( "%d", &TestCases );
for( ; TestCases--; )
Cal();
return 0;
}
int Vis[ Maxn ], Prime[ Maxn ], Num, Mu[ Maxn ], Sum[ Maxn ];
long long F[ Maxn ];
void EulerSieve() {
Mu[ 1 ] = 1;
for( int i = 2; i < Maxn; ++i ) {
if( !Vis[ i ] ) {
Prime[ ++Num ] = i;
Mu[ i ] = -1;
}
for( int j = 1; j <= Num && 1ll * i * Prime[ j ] < 1ll * Maxn; ++j ) {
Vis[ i * Prime[ j ] ] = 1;
if( i % Prime[ j ] == 0 ) break;
Mu[ i * Prime[ j ] ] = -Mu[ i ];
}
}
for( int i = 1; i < Maxn; ++i ) Sum[ i ] = Sum[ i - 1 ] + Mu[ i ];
for( int i = 1; i < Maxn; ++i )
for( int l = 1, r; l <= i; l = r + 1 ) {
r = i / ( i / l );
F[ i ] += 1ll * ( r - l + 1 ) * ( i / l );
}
return;
}
void Cal() {
int N, M;
scanf( "%d%d", &N, &M );
long long Ans = 0;
for( int i = 1, j; i <= N && i <= M; i = j + 1 ) {
j = min( N / ( N / i ), M / ( M / i ) );
Ans += 1ll * ( Sum[ j ] - Sum[ i - 1 ] ) * F[ N / i ] * F[ M / i ];
}
printf( "%lld\n", Ans );
return;
}