题意:已知d(x)为x的约数个数,给定N,M(<=50000),求:
∑
i
=
1
n
∑
j
=
1
m
d
(
i
j
)
\sum_{i=1}^n \sum_{j=1}^md(ij)
i=1∑nj=1∑md(ij)
思路:
直接利用公式:
d
(
n
m
)
=
∑
i
∣
n
∑
j
∣
m
[
g
c
d
(
i
,
j
)
=
=
1
]
d(nm) = \sum_{i|n}\sum_{j|m}[gcd(i,j) == 1]
d(nm)=i∣n∑j∣m∑[gcd(i,j)==1]
前缀和:
∑
i
=
1
n
∑
j
=
1
m
d
(
i
j
)
=
∑
i
=
1
n
∑
j
=
1
m
⌊
n
i
⌋
⌊
m
j
⌋
[
g
c
d
(
i
,
j
)
=
=
1
]
\sum_{i=1}^n \sum_{j=1}^md(ij) = \sum_{i=1}^n \sum_{j=1}^m\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{j}\rfloor[gcd(i,j) == 1]
i=1∑nj=1∑md(ij)=i=1∑nj=1∑m⌊in⌋⌊jm⌋[gcd(i,j)==1]
证明参考博客
什么二大重数学归纳,反正我是没看懂= =
不过直接考虑质因子的贡献还是比较好想通的。
这样的话,设答案为Ans,则:
A
n
s
=
∑
i
=
1
n
∑
j
=
1
m
⌊
n
i
⌋
⌊
m
j
⌋
[
g
c
d
(
i
,
j
)
=
=
1
]
Ans = \sum_{i=1}^n \sum_{j=1}^m\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{j}\rfloor[gcd(i,j) == 1]
Ans=i=1∑nj=1∑m⌊in⌋⌊jm⌋[gcd(i,j)==1]
由
∑
d
∣
n
μ
(
d
)
=
[
n
=
=
1
]
\sum_{d|n}\mu(d) = [n == 1]
∑d∣nμ(d)=[n==1],得:
A
n
s
=
∑
i
=
1
n
∑
j
=
1
m
⌊
n
i
⌋
⌊
m
j
⌋
∑
d
∣
g
c
d
(
i
,
j
)
μ
(
d
)
Ans=\sum_{i=1}^n \sum_{j=1}^m\lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{j}\rfloor \sum_{d|gcd(i,j)}\mu(d)
Ans=i=1∑nj=1∑m⌊in⌋⌊jm⌋d∣gcd(i,j)∑μ(d)
转换枚举变量,得:
A
n
s
=
∑
d
=
1
m
i
n
(
n
,
m
)
μ
(
d
)
∑
i
=
1
⌊
n
d
⌋
⌊
n
i
d
⌋
∑
j
=
1
⌊
m
d
⌋
⌊
m
j
d
⌋
Ans = \sum_{d=1}^{min(n,m)}\mu(d) \sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\lfloor\frac{n}{id}\rfloor\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}\lfloor\frac{m}{jd}\rfloor
Ans=d=1∑min(n,m)μ(d)i=1∑⌊dn⌋⌊idn⌋j=1∑⌊dm⌋⌊jdm⌋
令: f ( x ) = ∑ i = 1 x ⌊ x i ⌋ f(x) = \sum_{i=1}^x\lfloor\frac{x}{i}\rfloor f(x)=∑i=1x⌊ix⌋
得出:
A
n
s
=
∑
d
=
1
m
i
n
(
n
,
m
)
μ
(
d
)
f
(
⌊
n
d
⌋
)
f
(
⌊
m
d
⌋
)
Ans = \sum_{d=1}^{min(n,m)}\mu(d)f(\lfloor\frac{n} {d}\rfloor)f(\lfloor\frac{m}{d}\rfloor)
Ans=d=1∑min(n,m)μ(d)f(⌊dn⌋)f(⌊dm⌋)
故 O ( n n ) O(n \sqrt n) O(nn)预处理 f ( x ) f(x) f(x)然后分块就好啦
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 5e4 + 10;
int pri[A],tot;
bool vis[A];
ll d[A],mu[A];
void init(){
tot = 0;mu[1] = 1;
for(int i=2 ;i<A ;i++){
if(!vis[i]){mu[i] = -1;pri[++tot] = i;}
for(int j=1 ;j<=tot && pri[j]*i<A ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){
mu[i*pri[j]] = 0;
break;
}
mu[i*pri[j]] = -mu[i];
}
}
int last;
for(int i=1 ;i<A ;i++){
mu[i] += mu[i-1];
for(int j=1 ;j<=i ;j=last+1){
last = i/(i/j);
d[i] += 1LL*(i/j)*(last - j + 1);
}
}
}
int main(){
init();
int T;
scanf("%d",&T);
while(T--){
int n,m,last;
scanf("%d%d",&n,&m);
if(n>m) swap(n,m);
ll ans = 0;
for(int i=1 ;i<=n ;i=last+1){
last = min(n/(n/i),m/(m/i));
ans += 1LL*(mu[last] - mu[i-1])*d[n/i]*d[m/i];
}
printf("%lld\n",ans);
}
return 0;
}
后来看网上博客都说
f
(
x
)
f(x)
f(x)显然是
d
(
x
)
d(x)
d(x)的前缀。
推导了一下确实如此。
对于前n个约数个数之和:
∑
i
=
1
n
d
(
i
)
=
∑
i
=
1
n
∑
j
=
1
n
[
j
∣
i
]
=
∑
i
=
1
n
∑
j
=
1
n
[
i
∣
j
]
=
∑
i
=
1
n
⌊
n
i
⌋
\sum_{i=1}^nd(i) = \sum_{i=1}^n\sum_{j=1}^n[j|i] = \sum_{i=1}^{n}\sum_{j=1}^n[i|j] = \sum_{i=1}^n \lfloor\frac{n}{i}\rfloor
i=1∑nd(i)=i=1∑nj=1∑n[j∣i]=i=1∑nj=1∑n[i∣j]=i=1∑n⌊in⌋
故可以优化到 O ( n ) O(n) O(n)完成预处理。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 5e4 + 10;
int pri[A],cnt[A],tot;
bool vis[A];
ll d[A],mu[A];
void init(){
tot = 0;mu[1] = d[1] = 1;
for(int i=2 ;i<A ;i++){
if(!vis[i]){mu[i] = -1;pri[++tot] = i;d[i] = 2;cnt[i] = 1;}
for(int j=1 ;j<=tot && pri[j]*i<A ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){
d[i*pri[j]] = d[i]/(cnt[i]+1)*(cnt[i]+2);
cnt[i*pri[j]] = cnt[i] + 1;
mu[i*pri[j]] = 0;
break;
}
d[i*pri[j]] = d[i]<<1;
cnt[i*pri[j]] = 1;
mu[i*pri[j]] = -mu[i];
}
}
for(int i=1 ;i<A ;i++){
mu[i] += mu[i-1];
d[i] += d[i-1];
}
}
int main(){
init();
int T;
scanf("%d",&T);
while(T--){
int n,m,last;
scanf("%d%d",&n,&m);
if(n>m) swap(n,m);
ll ans = 0;
for(int i=1 ;i<=n ;i=last+1){
last = min(n/(n/i),m/(m/i));
ans += 1LL*(mu[last] - mu[i-1])*d[n/i]*d[m/i];
}
printf("%lld\n",ans);
}
return 0;
}