Description
有一张 n×m 的数表,其第 i 行第 j 列(1 <= i <= n, 1 <= j <= m)的数值为
能同时整除 i 和 j 的所有自然数之和。给定 a , 计算数表中不大于 a 的数之和。
Input
输入包含多组数据。
输入的第一行一个整数Q表示测试点内的数据组数
接下来Q行,每行三个整数n,m,a(|a| < =10^9)描述一组数据。
1
<
=
n
.
m
<
=
1
0
5
,
1
<
=
Q
<
=
2
×
1
0
4
1 < =n.m < =10^5 , 1 < =Q < =2×10^4
1<=n.m<=105,1<=Q<=2×104
Output
对每组数据,输出一行一个整数,表示答案模2^31的值。
Sample Input
2
4 4 3
10 10 5
Sample Output
20
148
这题出得很刁钻……
预备知识(莫比乌斯反演)
思路:
莫比乌斯反演+树状数组维护。
1.推导公式。
2.理解树状数组的用处。
3.线性筛中的细节。
4.小优化。
1.推导公式
首先,我们设两个函数:
F
(
a
)
=
⌊
n
/
a
⌋
F(a)=\left\lfloor\ n/a \right\rfloor
F(a)=⌊ n/a⌋*
⌊
m
/
a
⌋
\left\lfloor\ m/a \right\rfloor
⌊ m/a⌋
表示
a
∣
g
c
d
(
i
,
j
)
a|gcd(i,j)
a∣gcd(i,j)的方案数.
f
(
a
)
f(a)
f(a)表示
a
=
g
c
d
(
i
,
j
)
a=gcd(i,j)
a=gcd(i,j)的方案数。
显然,
F
(
a
)
=
∑
a
∣
d
f
(
d
)
F(a)=\sum_{a|d} f(d)
F(a)=∑a∣df(d)。
设n<=m,那么,我们忽略a的存在,总方案数就是
∑
d
=
1
n
∑
i
=
1
n
∑
j
=
1
n
f
[
d
]
∗
[
g
c
d
(
i
,
j
)
=
=
d
]
(
这
是
b
o
o
l
表
达
式
,
真
为
1
,
假
为
0
)
\sum_{d=1}^n \sum_{i=1}^n \sum_{j=1}^n f[d]*[gcd(i,j)==d](这是bool表达式,真为1,假为0)
∑d=1n∑i=1n∑j=1nf[d]∗[gcd(i,j)==d](这是bool表达式,真为1,假为0)=
∑
d
=
1
n
∑
i
=
1
⌊
n
/
d
⌋
∑
j
=
1
⌊
m
/
d
⌋
f
[
d
]
∗
[
g
c
d
(
i
,
j
)
=
=
1
]
\sum_{d=1}^n \sum_{i=1}^{\left\lfloor\ n/d \right\rfloor} \sum_{j=1}^{\left\lfloor\ m/d \right\rfloor} f[d]*[gcd(i,j)==1]
∑d=1n∑i=1⌊ n/d⌋∑j=1⌊ m/d⌋f[d]∗[gcd(i,j)==1](把d当作部分单位元或者说只有i为d的倍数且j为d的倍数时,才满足
d
∣
g
c
d
(
i
,
j
)
d|gcd(i,j)
d∣gcd(i,j))=
∑
d
=
1
n
f
[
d
]
∗
∑
i
=
1
⌊
n
/
d
⌋
∑
j
=
1
⌊
m
/
d
⌋
∑
k
∣
i
且
k
∣
j
μ
[
k
]
\sum_{d=1}^n f[d]*\sum_{i=1}^{\left\lfloor\ n/d \right\rfloor} \sum_{j=1}^{\left\lfloor\ m/d \right\rfloor}\sum_{k|i且k|j}\mu[k]
∑d=1nf[d]∗∑i=1⌊ n/d⌋∑j=1⌊ m/d⌋∑k∣i且k∣jμ[k]=
∑
d
=
1
n
f
[
d
]
∗
∑
k
=
1
⌊
n
/
d
⌋
μ
[
k
]
∗
F
[
k
∗
d
]
\sum_{d=1}^n f[d]*\sum_{k=1}^{\left\lfloor\ n/d \right\rfloor}\mu[k]*F[k*d]
∑d=1nf[d]∗∑k=1⌊ n/d⌋μ[k]∗F[k∗d]=
∑
d
=
1
n
f
[
d
]
∗
∑
d
∣
i
n
μ
[
i
/
d
]
∗
F
[
i
]
\sum_{d=1}^n f[d]*\sum_{d|i}^n\mu[i/d]*F[i]
∑d=1nf[d]∗∑d∣inμ[i/d]∗F[i](又把1当作单位元)=
(改变枚举顺序)
∑
i
=
1
n
F
[
i
]
∑
d
∣
i
f
[
d
]
∗
μ
[
i
/
d
]
\sum_{i=1}^n F[i] \sum_{d|i} f[d]*\mu[i/d]
∑i=1nF[i]∑d∣if[d]∗μ[i/d].
对于
f
[
i
]
f[i]
f[i]我们可以用整除分块来减少枚举。
2.理解树状数组的用处
此时再来考虑a的影响。我们可以用离线处理数据,并让a非降序排列。
如果
f
[
x
]
<
=
a
f[x]<=a
f[x]<=a,那么就可以用x为
F
[
i
]
F[i]
F[i]贡献系数。
还是为了减少枚举,我们边线性筛,边处理f,并以约数和从小到大排序f。
对于每个
f
[
x
]
<
=
a
f[x]<=a
f[x]<=a,我们令x去贡献系数,并用树状数组维护系数。
3.线性筛中的细节
对于线性筛,我们的任务是求
μ
和
f
\mu和f
μ和f,至于
μ
\mu
μ的求法这里不细讲(不会请翻预备知识),这里提一下f的求法。
方法一:时间复杂度
O
(
N
l
n
N
)
≈
O
(
N
l
o
g
N
)
O(NlnN)\approx O(NlogN)
O(NlnN)≈O(NlogN)
for(int i=1;i<=10000000;i++)//Eratosthenes筛法
for(int j=i;j<=10000000;j+=i)
f[j]+=i;
方法二:时间复杂度
O
(
N
)
O(N)
O(N)
根据约数和公式:若
x
=
p
1
c
1
∗
p
2
c
2
∗
…
…
p
k
c
k
x=p_1^{c_1}*p_2^{c_2}*……p_k^{c_k}
x=p1c1∗p2c2∗……pkck,
则
f
(
x
)
=
∏
i
=
1
k
∑
j
=
0
c
i
p
i
j
f(x)=\prod_{i=1}^k\sum_{j=0}^{c_i}p_i^j
f(x)=∏i=1k∑j=0cipij
那么我们可以实现简单继承:
当
p
j
∣
i
p_j|i
pj∣i时,
设
k
=
∏
i
≠
y
p
i
c
i
,
f
(
i
∗
p
j
)
=
f
(
k
)
∗
p
y
c
y
+
1
+
f
[
i
]
=
f
(
k
)
+
f
(
i
)
∗
p
y
设k=\prod_{i\ne y}p_i^{c_i},f(i*p_j)=f(k)*p_y^{c_y+1}+f[i]=f(k)+f(i)*p_y
设k=∏i̸=ypici,f(i∗pj)=f(k)∗pycy+1+f[i]=f(k)+f(i)∗py(读者可自行证明,其实就是代入公式,这里不赘述)
否则,
f
[
i
∗
p
y
]
=
f
[
i
]
∗
f
[
p
y
]
(
约
数
和
是
积
性
函
数
,
读
者
亦
可
自
行
证
明
,
其
实
又
是
代
入
公
式
)
f[i*p_y]=f[i]*f[p_y](约数和是积性函数,读者亦可自行证明,其实又是代入公式)
f[i∗py]=f[i]∗f[py](约数和是积性函数,读者亦可自行证明,其实又是代入公式)
4.一个小优化:
因为答案要 m o d 2 31 mod \ 2^{31} mod 231,我们就用int存答案,溢出其实是 m o d 2 32 mod \ 2^{32} mod 232(需要理解),所以不影响答案,只是答案为负就转为正数即可。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int M=2e4+10;
const ll mod=1LL<<31;
void qr(int &x)
{
char c=g;x=0;bool v=0;
while(!(('0'<=c&&c<='9')||c=='-'))c=g;
if(c=='-')v=1,c=g;
while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
if(v)x=-x;
}
void write(int x)
{
if(x/10)write(x/10);
putchar(x%10+'0');
}
struct node
{
int n,m,a,id;
bool operator <(node b)const{return a<b.a;}
}a[M];
struct pnode
{
int s,id;
bool operator <(pnode b)const{return s<b.s;}
}f[N];
int T,n,m,prime[N/10],tot,miu[N],mo[N],ans[M],maxn,tr[N];
//f[i]表示i的约数和,对于mo[i](mother),设i除去最小的质数的所有因子后得到k,mo[i]=f[k];
bool v[N];
void get_prime()
{
miu[1]=f[1].s=1;
for(int i=2;i<=maxn;i++)
{
if(!v[i])prime[++tot]=i,miu[i]=-1,mo[i]=1,f[i].s=i+1;
for(int j=1,k;j<=tot&&(ll)i*prime[j]<=maxn;j++)
{
v[k=i*prime[j]]=1;
if(i%prime[j]==0)
{
miu[k]=0;
f[k].s=mo[i] + f[i].s*prime[j];
mo[k]=mo[i];
break;
}
else
{
miu[k]=-miu[i];
f[k].s=f[i].s*f[prime[j]].s;
mo[k]=f[i].s;
}
}
}
for(int i=1;i<=maxn;i++)f[i].id=i;
sort(f+1,f+maxn+1);
}
int lowbit(int x){return x&-x;}
void add(int x,int v)
{
while(x<=maxn)
{
tr[x]+=v;
x+=lowbit(x);
}
}
void change(int i)
{
int d=f[i].id,s=f[i].s;
for(int j=1;j*d<=maxn;j++)if(miu[j])
add(d*j,miu[j]*s);
}
int solve(int x)
{
int tot=0;
while(x>0)
{
tot+=tr[x];
x-=lowbit(x);
}
return tot;
}
int main()
{
qr(T);
for(int i=1;i<=T;i++)
{
qr(a[i].n);qr(a[i].m);qr(a[i].a);a[i].id=i;
if(a[i].n>a[i].m)swap(a[i].n,a[i].m);
if(a[i].n>maxn)maxn=a[i].n;
}
sort(a+1,a+T+1);
get_prime();
for(int i=1,j=1;i<=T;i++)
{
while(f[j].s<=a[i].a&&j<=N-10)change(j++);
int &sum=ans[a[i].id],n=a[i].n,m=a[i].m;
sum=0;
for(int l=1,r;l<=n;l=r+1)
{
r=min(n/(n/l),m/(m/l));
sum+=(solve(r)-solve(l-1))*(n/l)*(m/l);
}
if(sum<0)sum+=mod;
}
for(int i=1;i<=T;i++)write(ans[i]),puts("");
return 0;
}