题意:令
F
(
i
)
F(i)
F(i)为i的约数和。给定q个询问,每个询问包含n,m,a。
求
A
n
s
=
∑
x
=
1
n
∑
y
=
1
m
F
(
g
c
d
(
x
,
y
)
)
(
F
(
g
c
d
(
x
,
y
)
)
<
=
a
)
Ans = \sum_{x=1}^n \sum_{y=1}^m F(gcd(x,y)) \space (F(gcd(x,y))<=a)
Ans=x=1∑ny=1∑mF(gcd(x,y)) (F(gcd(x,y))<=a)
课件里面讲得很详细,但其中的细节讲解可能对初学者不太友好。
我再简单把思路捋一遍。
首先如果直接双重循环枚举
x
,
y
x,y
x,y肯定是超时的,我们可以考虑转化一下:
因为不同的
x
,
y
x,y
x,y可能会有多组相同的
g
c
d
gcd
gcd,我们可以考虑算出每一组
g
c
d
gcd
gcd的个数。
设
g
(
i
)
:
g
c
d
(
x
,
y
)
=
i
的
方
案
数
g(i): gcd(x,y)=i的方案数
g(i):gcd(x,y)=i的方案数
则我们可以直接枚举
g
c
d
gcd
gcd求转化问题:
A
n
s
=
∑
i
=
1
m
i
n
(
n
,
m
)
F
(
i
)
g
(
i
)
(
F
(
i
)
<
=
a
)
Ans = \sum_{i=1}^{min(n,m)}F(i)g(i) \space (F(i) <= a)
Ans=i=1∑min(n,m)F(i)g(i) (F(i)<=a)
对于
F
(
i
)
F(i)
F(i),我们可以在
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的复杂度下预处理出来。
而对于
g
(
i
)
g(i)
g(i)的求解,可以考虑莫比乌斯反演。
设
G
(
i
)
:
g
c
d
(
x
,
y
)
为
i
的
倍
数
的
方
案
数
G(i):gcd(x,y)为i的倍数的方案数
G(i):gcd(x,y)为i的倍数的方案数
G
(
i
)
=
∑
i
∣
d
g
(
d
)
=
>
g
(
i
)
=
∑
i
∣
d
μ
(
d
i
)
G
(
d
)
=
∑
i
∣
d
μ
(
d
i
)
⌊
n
d
⌋
⌊
m
d
⌋
G(i) = \sum_{i|d}g(d) => g(i)=\sum_{i|d}\mu(\frac{d}{i})G(d) = \sum_{i|d}\mu(\frac{d}{i})\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor
G(i)=i∣d∑g(d)=>g(i)=i∣d∑μ(id)G(d)=i∣d∑μ(id)⌊dn⌋⌊dm⌋
将
g
(
i
)
g(i)
g(i)代入
A
n
s
Ans
Ans表达式:
A
n
s
=
∑
i
=
1
m
i
n
(
n
,
m
)
F
(
i
)
∑
i
∣
d
μ
(
d
i
)
⌊
n
d
⌋
⌊
m
d
⌋
(
F
(
i
)
<
=
a
)
Ans = \sum_{i=1}^{min(n,m)}F(i)\sum_{i|d}\mu(\frac{d}{i})\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor \space (F(i) <= a)
Ans=i=1∑min(n,m)F(i)i∣d∑μ(id)⌊dn⌋⌊dm⌋ (F(i)<=a)
因考虑到需要使用分块除法来降低复杂度,故对于
d
d
d的枚举必须连续,故我们转换第一维的枚举变量:
A
n
s
=
∑
d
=
1
m
i
n
(
n
,
m
)
⌊
n
d
⌋
⌊
m
d
⌋
∑
i
∣
d
μ
(
d
i
)
F
(
i
)
(
F
(
i
)
<
=
a
)
Ans = \sum_{d=1}^{min(n,m)}\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor \space \sum_{i|d}\mu(\frac{d}{i})F(i) (F(i) <= a)
Ans=d=1∑min(n,m)⌊dn⌋⌊dm⌋ i∣d∑μ(id)F(i)(F(i)<=a)
此时若没有
a
a
a的限制,我们可以直接预处理前缀和来快速处理第二个求和式。
但因为存在
a
a
a,故我们需要离线处理,将询问按
a
a
a的大小进行排序。
借助树状数组,位置
x
x
x维护的是
∑
i
∣
x
F
(
i
)
μ
(
x
i
)
\sum_{i|x}F(i)\mu(\frac{x}{i})
i∣x∑F(i)μ(ix)
然后就可以分块除法,快速求和了。
总的复杂度是
O
(
n
l
o
g
2
n
+
q
n
l
o
g
n
)
O(nlog^2n + q\sqrt nlogn)
O(nlog2n+qnlogn)
其中
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n) 是
O
(
n
)
O(n)
O(n)枚举
1
−
n
1-n
1−n的数,
O
(
l
o
g
n
)
O(logn)
O(logn)是枚举倍数,
O
(
l
o
g
n
)
O(logn)
O(logn)是树状数组插入操作。
而
O
(
q
n
l
o
g
n
)
O(q\sqrt nlogn)
O(qnlogn)是
O
(
q
)
O(q)
O(q)枚举每一个询问,
O
(
n
)
O(\sqrt n)
O(n) 是分块除法,
O
(
l
o
g
n
)
O(logn)
O(logn)是树状数组查询操作。
至此,大功告成。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 1e5 + 10;
class Query{
public:
int n,m,a,id;
bool operator<(const Query& rhs)const{
return a < rhs.a;
}
}Q[A];
bool vis[A];
int pri[A],mu[A],Bit[A],Ans[A],Max,tot;
pair<int,int> F[A];
void init(){
mu[1] = 1;
for(int i=2 ;i<=Max ;i++){
if(vis[i] == 0){pri[++tot] = i;mu[i]=-1;}
for(int j=1 ;j<=tot&&i*pri[j]<=Max ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){mu[i*pri[j]] = 0;break;}
mu[i*pri[j]] = -mu[i];
}
}
for(int i=1 ;i<=Max ;i++){
F[i].second = i;
for(int j=i ;j<=Max ;j+=i){
F[j].first += i;
}
}
}
void update(int pos,int val){
for(int i=pos ;i<A ;i+=i&(-i)) Bit[i] += val;
}
int query(int pos){
int res = 0;
for(int i=pos ;i>0 ;i-=i&(-i)) res += Bit[i];
return res;
}
void solve(int x){
int n = Q[x].n,m = Q[x].m;
int ans = 0,last;
for(int i=1 ;i<=n ;i=last+1){
last = min(n/(n/i),m/(m/i));
ans += (n/i)*(m/i)*(query(last) - query(i-1));
}
Ans[Q[x].id] = ans;
}
int main(){
int q;
scanf("%d",&q);Max = 0;
for(int i=0 ;i<q ;i++){
scanf("%d%d%d",&Q[i].n,&Q[i].m,&Q[i].a);
if(Q[i].n > Q[i].m) swap(Q[i].n,Q[i].m);
Q[i].id = i;Max = max(Max,Q[i].n);
}
sort(Q,Q+q);
init();
sort(F+1,F+Max+1);
int now = 0;
for(int i=0 ;i<q ;i++){
while(now+1<=Max && F[now+1].first<=Q[i].a){
now++;
for(int j=F[now].second ;j <= Max ;j+=F[now].second){
update(j,F[now].first*mu[j/F[now].second]);
}
}
solve(i);
}
for(int i=0 ;i<q ;i++) printf("%d\n",Ans[i]&0x7fffffff);
return 0;
}