题解 P3704 【[SDOI2017]数字表格】

Luogu P3704 [SDOI2017]数字表格


同步更新在我的莫比乌斯反演做题记录,里面有一些莫反的套路题和基础前置芝士。


在机房复盘 SDOI 2017 的模拟赛第一次场 A 黑题写题解纪念一下。

这个题要你求:

\[\prod_{i=1}^n\prod_{j=1}^m \mathrm{fib_{\gcd(i,j)}} \]

其中 \(\mathrm{fib_{i}}\) 表示斐波拉契数列第 \(i\) 项。

\(\prod\) 连乘运算做不了 \(\sum\) 的一些卷积公式(尤其是 \(\texttt{Dirichlet}\) 卷积)。我们考虑把这个式子换成可以用 \(\sum\) 推的情况。

首先一般性地,我们保证 \({\color{red}{n\leq m}}\)

我们枚举 \(\displaystyle\sum_i\sum_j f(i,j)\) 这一类式子的时候会习惯性地枚举一个 \(k\) 让其满足和式内部的条件。

这题即使是 \(\prod\) 也可以套路化地使用这个方法。

所以原式可以转换为

\[\prod_{k=1}^n\prod_{i=1}^n\prod_{j=1}^m \mathrm{fib}_k\times[\gcd(i,j)=k] \]

我们发现每个 \(\mathrm{fib}_d\) 都会与自己相乘很多次,于是我们可以将每个 \(d\) 的贡献考虑写成幂的形式。

显然每个 \(\mathrm{fib}_d\) 的幂的指数就是其等于 \(\gcd(i,j)\) 对应的数对 \((i,j)\) 的个数。

现在我们有

\[\prod_{k=1}^n\mathrm {fib}_k^{\displaystyle\bigg({\color{blue}{\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=k]}}\bigg)} \]

我们把指数特地标记出来。因为我们发现他是一个老朋友。

我们在做形似 \(\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)。

\[\begin{aligned} {\color{blue}{\texttt{blue part}}}&=\sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{k}\rfloor}[\gcd(i,j)=1]\\ &=\sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{k}\rfloor}\epsilon(\gcd(i,j))\\ &=\sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{k}\rfloor}\sum_{d|\gcd(i,j)}\mu(d)\\ &=\sum_{d=1}^{\lfloor\frac{n}{k}\rfloor}\mu(d)\sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}[ d|i]\sum_{j=1}^{\lfloor \frac{m}{k}\rfloor}[d|j]\\ &=\sum_{d=1}^{\lfloor\frac{n}{k}\rfloor}\mu(d)\lfloor\frac{n}{k\times d}\rfloor\lfloor\frac{m}{k\times d}\rfloor \end{aligned} \]

这个东西已经可以 \(\Theta(\sqrt n)\) 级别的数论分块做了,但是我们要把其代入原式继续看。

现在原式变成了:

\[\prod_{k=1}^n\mathrm {fib}_k^{\bigg (\displaystyle\sum_{d=1}^{\lfloor\frac{n}{k}\rfloor}\mu(d)\lfloor\frac{n}{k\times d}\rfloor\lfloor\frac{m}{k\times d}\rfloor\bigg)} \]

我们设 \(T=k\times d\)

我们发现这样方便让我们将 \(\displaystyle\sum_{d=1}^{\lfloor \frac{n}{k}\rfloor}\)\(\mathrm{fib}_k\) 以幂的形式结合。所有变量用 \(T\) 表示出来后贡献到一个 \(\prod\) 中。

写出来就是:

\[\prod_{T=1}^n\bigg({\color{blue}{\prod_{k|T}\mathrm{fib}_k^{\mu(\frac{T}{k})}}}\bigg)^{\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor} \]

蓝色部分我们可以通过类似筛法预处理的方法求出:

  • 线筛 \(\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;
}
posted @ 2021-03-31 18:37  Reywmp  阅读(77)  评论(0编辑  收藏  举报