题解 P3704 【[SDOI2017]数字表格】
Luogu P3704 [SDOI2017]数字表格
同步更新在我的莫比乌斯反演做题记录,里面有一些莫反的套路题和基础前置芝士。
在机房复盘 SDOI 2017 的模拟赛第一次场 A 黑题写题解纪念一下。
这个题要你求:
其中 \(\mathrm{fib_{i}}\) 表示斐波拉契数列第 \(i\) 项。
\(\prod\) 连乘运算做不了 \(\sum\) 的一些卷积公式(尤其是 \(\texttt{Dirichlet}\) 卷积)。我们考虑把这个式子换成可以用 \(\sum\) 推的情况。
首先一般性地,我们保证 \({\color{red}{n\leq m}}\)。
我们枚举 \(\displaystyle\sum_i\sum_j f(i,j)\) 这一类式子的时候会习惯性地枚举一个 \(k\) 让其满足和式内部的条件。
这题即使是 \(\prod\) 也可以套路化地使用这个方法。
所以原式可以转换为
我们发现每个 \(\mathrm{fib}_d\) 都会与自己相乘很多次,于是我们可以将每个 \(d\) 的贡献考虑写成幂的形式。
显然每个 \(\mathrm{fib}_d\) 的幂的指数就是其等于 \(\gcd(i,j)\) 对应的数对 \((i,j)\) 的个数。
现在我们有
我们把指数特地标记出来。因为我们发现他是一个老朋友。
我们在做形似 \(\displaystyle\sum_{i=1}^n\sum_{j=1}^n\gcd(i,j)\) 的一类莫反题是会将式子转换成 \(\displaystyle \sum_{k=1}^n\bigg(k{\color{blue}{\sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=k]}}\bigg)\)
我们考虑怎么将这个式子转化成可做的形式(以下皆是莫反题老套路,跟不上请先去做一些基础莫反题,不是幽灵乐团,别信 CYJian)。
这个东西已经可以 \(\Theta(\sqrt n)\) 级别的数论分块做了,但是我们要把其代入原式继续看。
现在原式变成了:
我们设 \(T=k\times d\)。
我们发现这样方便让我们将 \(\displaystyle\sum_{d=1}^{\lfloor \frac{n}{k}\rfloor}\) 与 \(\mathrm{fib}_k\) 以幂的形式结合。所有变量用 \(T\) 表示出来后贡献到一个 \(\prod\) 中。
写出来就是:
蓝色部分我们可以通过类似筛法预处理的方法求出:
- 线筛 \(\mu\) 函数。
- 对于每一个 \(k\) 枚举其倍数 \(T\) ,乘上这个 \(\mathrm{fib}_k\) 算入这个 \(T\) 的贡献。
- 因为 \(\mu\) 只有三种取值,有影响的只有 \(\{1,-1\}\) 两种。
- 所以问题就变成了乘上 \(\mathrm {fib}_{k}\) 或除以 \(\mathrm {fib}_k\)。这里就变成 \(\mathrm{inv fib}\)(逆元)和 \(\mathrm{fib}\) 两种情况。
外层我们做数论分块的时候,每次求内部只需要一次 \(\Theta (\log p)\)(\(p\) 是模数)和 \(\Theta(\log(\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor))\)的快速幂分别求逆元和与外层指数的幂。
预处理复杂度是 \(\Theta(n\log n+n\log p)\),数论分块复杂度是 \(\Theta(t\sqrt n (\log p+\log \lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor))\) 小写 \(t\) 是数据组数。
实现看代码(考试时忘了写逆元卡了好久导致 T2 LCT 都没写完):
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define INL inline
#define Re register
//Tosaka Rin Suki~
INL int read()
{
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();if(ch=='-')w=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-48,ch=getchar();return x*w;
}
const int N=1e6+5;
const ll MOD=1e9+7;
ll f[N],finv[N],prime[N],mu[N],cnt,prod[N];
bool vis[N];
INL ll fpow(ll x,ll p)
{
ll res=1;
while(p)
{
if(p&1)res=(1ll*res*x)%MOD;
x=(1ll*x*x)%MOD;p>>=1;
}
return res;
}
INL void get_mu(ll n)
{
f[0]=0,f[1]=1;
for(int i=2;i<=n;i++)f[i]=(f[i-1]+f[i-2])%MOD;
for(int i=0;i<=n;i++)finv[i]=fpow(f[i],MOD-2),prod[i]=1;
//fib 和 invfib
vis[1]=1,vis[0]=1,mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i])prime[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt&&prime[j]*i<=n;j++)
{
vis[prime[j]*i]=1;
if(i%prime[j])mu[i*prime[j]]=-mu[i];
else {mu[i*prime[j]]=0;break;}
}
}//筛出 mu
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j+=i)
{
int now=j/i;
if(mu[now]==-1)prod[j]=1ll*prod[j]*finv[i]%MOD;
else if(mu[now]==1)prod[j]=1ll*prod[j]*f[i]%MOD;
}
}//每个 k 在其对应 \prod T 中的贡献
for(int i=1;i<=n;i++)prod[i]=1ll*prod[i-1]*prod[i]%MOD;
}
int main()
{
//freopen("product.in","r",stdin);
//freopen("product.out","w",stdout);
int T=read();
get_mu(1000000);
while(T--)
{
ll n=read(),m=read();
if(n<m)std::swap(n,m);
ll l=1,r,ans=1;
while(l<=m)
{
r=std::min(n/(n/l),m/(m/l));
ans=1ll*ans*fpow(1ll*prod[r]*fpow(prod[l-1],MOD-2)%MOD,1ll*(n/l)*(m/l)%(MOD-1))%MOD;
//数论分块,后面这个指数可以通过 mod MOD-1 减少量级
l=r+1;
}
printf("%lld\n",(ans%MOD+MOD)%MOD);
}
return 0;
}