「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;
}

总结

有一个套路是可以积累下来的:找到累乘与累加的关系,一般情况下(估计是针对莫比乌斯反演),累加是作为累乘的某个项的幂数

posted @ 2023-05-13 09:32  6penny  阅读(35)  评论(0编辑  收藏  举报