Description
分析:
对于这种式子,一般我们会枚举gcd(每个f值要使用几次)
设
g(d)=∑ni=1∑mj=1[gcd(i,j)=d]
g
(
d
)
=
∑
i
=
1
n
∑
j
=
1
m
[
g
c
d
(
i
,
j
)
=
d
]
把
∑ni=1∑mj=1[gcd(i,j)=d]
∑
i
=
1
n
∑
j
=
1
m
[
g
c
d
(
i
,
j
)
=
d
]
用反演化开
像我这种naive的选手,到这里就直接做了
继续,我们看看
g(d)
g
(
d
)
能不能进一步优化,设
T=kd
T
=
k
d
这样我们就可把
T
T
的枚举拉出来
枚举顺序换一下
发现对于每个
T
T
,的值是固定的,与
n
n
和无关,于是我们先用筛法预处理出每个
T
T
对应的这个式子的值,前缀积一下
而外面这一层可以分块处理
所以时间复杂度:
O((max(n,m)+T(n−−√+m−−√))logMOD)
O
(
(
m
a
x
(
n
,
m
)
+
T
(
n
+
m
)
)
l
o
g
M
O
D
)
tip
∏d|Tf(d)μ(Td)
∏
d
|
T
f
(
d
)
μ
(
T
d
)
直接暴力处理即可
设
sum
s
u
m
为前缀积,注意
sum[0]=1
s
u
m
[
0
]
=
1
一开始预处理就慢到爆,怀疑人生
KSM次数多了也费时,所以我们预处理f的逆元,减少KSM的调用
因为要计算
f(d)μ(Td)
f
(
d
)
μ
(
T
d
)
,而
μ
μ
<script type="math/tex" id="MathJax-Element-1569">μ</script>的取值只有-1,1,所以预处理f的逆元反而比较方便
多%防爆ll
还是免不了T掉了
ll改int,计算过程中强转ll
时间就少了一半诶
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int N=1000000;
const ll p=1e9+7;
int sshu[N+5],tot=0,mu[N+5],n,m;
int f[N+5],sum[N+5],inv[N+5];
bool no[N+5];
inline int KSM(int a,int b) {
int t=1;
a%=p;
while (b) {
if (b&1) t=(1LL*t*a)%p;
b>>=1;
a=(1LL*a*a)%p;
}
return t%p;
}
void prepare() {
mu[1]=1;
for (int i=2;i<=N;i++) {
if (!no[i]) {
sshu[++tot]=i;
mu[i]=-1;
}
for (int j=1;j<=tot&&sshu[j]*i<=N;j++) {
no[sshu[j]*i]=1;
if (i%sshu[j]==0) {
mu[sshu[j]*i]=0;
break;
}
mu[sshu[j]*i]=-mu[i];
}
}
f[0]=0; f[1]=1;
sum[0]=1; sum[1]=1; //前缀积
inv[1]=1; //f的逆元
for (int i=2;i<=N;i++) f[i]=(f[i-1]+f[i-2])%p,inv[i]=KSM(f[i],p-2),sum[i]=1;
for (int i=1;i<=N;i++)
if (mu[i]!=0) {
for (int j=1;j*i<=N;j++) // j*i=T i=T/d j=d
if (mu[i]>0) sum[j*i]=(1LL*sum[j*i]*f[j])%p;
else sum[j*i]=(1LL*sum[j*i]*inv[j])%p;
}
for (int i=2;i<=N;i++) sum[i]=(1LL*sum[i]*sum[i-1])%p;
}
int main()
{
prepare();
int T;
scanf("%d",&T);
while (T--) {
scanf("%d%d",&n,&m);
int last,ans=1;
for (int i=1;i<=min(n,m);i=last+1) {
last=min(n/(n/i),m/(m/i));
ans=1LL*ans%p*KSM(1LL*sum[last]*KSM(sum[i-1],p-2)%p,1LL*(n/i)*(m/i)%(p-1))%p;
}
printf("%d\n",ans%p);
}
return 0;
}