「SDOI2017」数字表格 题解
4114 「SDOI2017」数字表格 题解
个人评价:好题且套路多
算法标签
莫比乌斯反演
题目难度:2700
题面
分析题面
多组数据,每次给出\(n,m\),求解\(\prod_{i=1}^n\prod_{j=1}^mF_{gcd(i,j)}\),其中\(F\)是斐波那契数列
问题分析
第一眼化出这个式子肯定是一脸懵,毕竟我们现在做的大多数莫比乌斯反演都是求和,但是这个是求积,要想一想怎么才能找到乘积与求和的关系(当然不是把累乘变成累加),然后可以考虑到每个斐波那契数出现了多少次,出现次数累加起来作为这个斐波那契数的幂,似乎就可以做了
开始推式子啦:
\[\begin{aligned}
\prod_{i=1}^n\prod_{j=1}^mF_{gcd(i,j)}&=\prod_{k=1}^{min(n,m)}F_k^{\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)==k]}
\end{aligned}
\]
上面的次幂是老套路了,就不细写了,反正肯定是往莫比乌斯反演的那三个套路模型上面去靠,如果不知道的话,可以[看我](莫比乌斯反演常见的三个模型 - 6penny - 博客园 (cnblogs.com))
\[\begin{aligned}
\prod_{k=1}^{min(n,m)}F_k^{\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)==k]}&=\prod_{k=1}^{min(n,m)}F_k^{\sum_{d=1}^{min(n,m)}\lfloor\frac{n}{kd}\rfloor\lfloor\frac{m}{kd}\rfloor\mu(d)}\\
&=\prod_{T=1}^n(\prod_{k|T}F_k^{\mu(\frac{T}{k})})^{\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor}
\end{aligned}
\]
说具体一点就是设\(T=kd\),用\(T\)去换元,把\(\mu\)放到斐波那契里面直接一起处理了
这个式子最后的样子也是老套路了,但是不能直接在筛的时候处理
实现细节
上面说到不能直接在筛的时候将内层求积计算出来,是因为斐波那契不是一个积性函数,所以我们直接在预处理的时候暴力将斐波那契数的\(\mu\)次方算出来
注意因为莫比乌斯函数有可能是\(-1\)所以要求逆元
注意随时随地要取模,血的教训
代码实现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll M=1e6+10,p=1e9+7;
ll pr[M],cnt,vis[M],mu[M],f[M],mul[M],inv[M];
ll qpow(ll x,ll y){
ll res=1;
while(y){
if(y&1)res=res*x%p;
x=x*x%p;
y>>=1;
}
return res%p;
}
void init(){
mu[1]=f[2]=f[1]=mul[1]=mul[0]=1;
inv[0]=0,inv[1]=1,inv[2]=1;
for(ll i=3;i<=M-10;i++){
f[i]=(f[i-1]+f[i-2])%p;
inv[i]=qpow(f[i],p-2)%p;
}
mul[1]=mul[0]=1;
for(ll i=2;i<=M-10;i++){
mul[i]=1;//注意不要memset,血的教训
if(!vis[i]){
vis[i]=1;
pr[++cnt]=i;
mu[i]=-1;
}
for(ll j=1;j<=cnt&&pr[j]*i<=M-10;j++){
vis[pr[j]*i]=1;
if(i%pr[j]==0){
mu[i*pr[j]]=0;
break;
}else{
mu[i*pr[j]]=-mu[i];
}
}
}
for(ll i=1;i<=M-10;i++){//暴力求解斐波那契的次幂的累乘
if(!mu[i])continue;
for(ll j=i;j<=M-10;j+=i){
if(mu[i]==1){
mul[j]=(mul[j]*f[j/i]%p+p)%p;
}else{
mul[j]=(mul[j]*inv[j/i]%p+p)%p;
}
}
}
for(ll i=1;i<=M-10;i++)mul[i]=(mul[i-1]*mul[i]%p+p)%p;
}
ll calc(ll x,ll y){
ll res=1;
for(ll l=1,r=0;l<=min(x,y);l=r+1){
r=min(x/(x/l),y/(y/l));
res=(res*qpow(mul[r]*qpow(mul[l-1],p-2)%p,(x/l)*(y/l))%p)%p;
//注意到这里就是最后的公式,因为是区间前缀积计算,所以除法得到区间积的话要用逆元
//求幂数的时候不要写着写着出来个取模,血的教训
}
return res%p;
}
void solve(){
ll n,m;
scanf("%lld%lld",&n,&m);
printf("%lld\n",calc(n,m)%p);
}
int main(){
ll T;
scanf("%lld",&T);
init();
while(T--)solve();
return 0;
}
总结
有一个套路是可以积累下来的:找到累乘与累加的关系,一般情况下(估计是针对莫比乌斯反演),累加是作为累乘的某个项的幂数