洛谷P3704 [SDOI2017]数字表格(预处理筛+莫比乌斯反演+乘法逆元+数论分块)
题目链接:https://www.luogu.com.cn/problem/P3704
题目大意:给定一组
n
,
m
n,m
n,m ,求
∏
i
=
1
n
∏
j
=
1
m
f
(
gcd
(
i
,
j
)
)
m
o
d
1
e
9
+
7
\prod\limits_{i=1}^n\prod\limits_{j=1}^mf(\gcd(i,j))\space mod\space 1e9+7
i=1∏nj=1∏mf(gcd(i,j)) mod 1e9+7 ,其中
f
(
x
)
f(x)
f(x) 为斐波那契数列的第
x
x
x 项。
题解:这次变成了求函数的连乘积,看上去无从下手,实际上还是与求函数的连加和一样的套路。不妨设
n
≤
m
n\leq m
n≤m 。
原式: ∏ i = 1 n ∏ j = 1 m f ( gcd ( i , j ) ) \prod\limits_{i=1}^n\prod\limits_{j=1}^mf(\gcd(i,j)) i=1∏nj=1∏mf(gcd(i,j))
= ∏ i = 1 n ∏ j = 1 m ∏ d = 1 n f ( d ) [ gcd ( i , j ) = d ] =\prod\limits_{i=1}^n\prod\limits_{j=1}^m\prod\limits_{d=1}^nf(d)^{[\gcd(i,j)=d]} =i=1∏nj=1∏md=1∏nf(d)[gcd(i,j)=d]
= ∏ d = 1 n ∏ i = 1 n ∏ j = 1 m f ( d ) [ gcd ( i , j ) = d ] =\prod\limits_{d=1}^n\prod\limits_{i=1}^n\prod\limits_{j=1}^mf(d)^{[\gcd(i,j)=d]} =d=1∏ni=1∏nj=1∏mf(d)[gcd(i,j)=d]
= ∏ d = 1 n ∏ i = 1 ⌊ n d ⌋ ∏ j = 1 ⌊ m d ⌋ f ( d ) ε ( gcd ( i , j ) ) =\prod\limits_{d=1}^n\prod\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\prod\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}f(d)^{\varepsilon (\gcd(i,j))} =d=1∏ni=1∏⌊dn⌋j=1∏⌊dm⌋f(d)ε(gcd(i,j))
= ∏ d = 1 n ∏ i = 1 ⌊ n d ⌋ ∏ j = 1 ⌊ m d ⌋ f ( d ) ∑ t ∣ i , t ∣ j μ ( t ) =\prod\limits_{d=1}^n\prod\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\prod\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}f(d)^{\sum\limits_{t\mid i,t\mid j}\mu(t)} =d=1∏ni=1∏⌊dn⌋j=1∏⌊dm⌋f(d)t∣i,t∣j∑μ(t)
= ∏ d = 1 n ∏ i = 1 ⌊ n d ⌋ ∏ j = 1 ⌊ m d ⌋ ∏ t ∣ i , t ∣ j f ( d ) μ ( t ) =\prod\limits_{d=1}^n\prod\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\prod\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}{\prod\limits_{t\mid i,t\mid j}}f(d)^{\mu(t)} =d=1∏ni=1∏⌊dn⌋j=1∏⌊dm⌋t∣i,t∣j∏f(d)μ(t)
= ∏ d = 1 n ∏ t = 1 ⌊ n d ⌋ ∏ i = 1 ⌊ n d t ⌋ ∏ j = 1 ⌊ m d t ⌋ f ( d ) μ ( t ) =\prod\limits_{d=1}^n\prod\limits_{t=1}^{\lfloor\frac{n}{d}\rfloor}\prod\limits_{i=1}^{\lfloor\frac{n}{dt}\rfloor}\prod\limits_{j=1}^{\lfloor\frac{m}{dt}\rfloor}f(d)^{\mu(t)} =d=1∏nt=1∏⌊dn⌋i=1∏⌊dtn⌋j=1∏⌊dtm⌋f(d)μ(t)
设 T = d t T=dt T=dt ,那么则可化为:
⇒ ∏ T = 1 n ∏ i = 1 ⌊ n T ⌋ ∏ j = 1 ⌊ m T ⌋ ∏ d ∣ T f ( d ) μ ( T d ) \Rightarrow\prod\limits_{T=1}^n\prod\limits_{i=1}^{\lfloor\frac{n}{T}\rfloor}\prod\limits_{j=1}^{\lfloor\frac{m}{T}\rfloor}\prod\limits_{d\mid T}f(d)^{\mu(\frac{T}{d})} ⇒T=1∏ni=1∏⌊Tn⌋j=1∏⌊Tm⌋d∣T∏f(d)μ(dT)
令
u
(
x
)
=
∏
d
∣
x
f
(
d
)
μ
(
x
d
)
u(x)=\prod\limits_{d\mid x}f(d)^{\mu(\frac{x}{d})}
u(x)=d∣x∏f(d)μ(dx) ,显然,这个函数可以使用埃式筛提前进行预处理,埃式筛可以遍历每一个数的每一个非1因数,而
μ
(
x
)
\mu(x)
μ(x) 和
f
(
x
)
f(x)
f(x) 也都能提前进行预处理。则经过预处理后,求任意
u
(
x
)
u(x)
u(x) 的值是
O
(
1
)
O(1)
O(1) 的。
此时原式等于:
⇒ ∏ T = 1 n ∏ i = 1 ⌊ n T ⌋ ∏ j = 1 ⌊ m T ⌋ u ( T ) \Rightarrow\prod\limits_{T=1}^n\prod\limits_{i=1}^{\lfloor\frac{n}{T}\rfloor}\prod\limits_{j=1}^{\lfloor\frac{m}{T}\rfloor}u(T) ⇒T=1∏ni=1∏⌊Tn⌋j=1∏⌊Tm⌋u(T)
= ∏ T = 1 n u ( T ) ⌊ n T ⌋ ⌊ m T ⌋ =\prod\limits_{T=1}^nu(T)^{{\lfloor\frac{n}{T}\rfloor}{\lfloor\frac{m}{T}\rfloor}} =T=1∏nu(T)⌊Tn⌋⌊Tm⌋
即可应用数论分块对原式进行计算加速。这样的话我们需要维护
u
(
x
)
u(x)
u(x) 的前缀积
p
u
(
x
)
pu(x)
pu(x) ,当计算一个区间
[
l
,
r
]
[l,r]
[l,r] 的场合,只需计算
p
u
(
r
)
/
p
u
(
l
−
1
)
pu(r)/pu(l-1)
pu(r)/pu(l−1) 。但这时便引申出求乘法逆元的问题。对于质数
1
e
9
+
7
1e9+7
1e9+7 ,求出
[
1
,
1
e
9
+
6
]
[1,1e9+6]
[1,1e9+6] 的所有逆元显然是不现实的,不妨先线性求出
[
1
,
1
e
6
]
[1,1e6]
[1,1e6] 的所有逆元,然后这个区间外的所有数的逆元便可以根据线性递推公式求出了,这种做法会比使用费马小定理直接求逆元要快。同时根据费马小定理,有
u
(
T
)
⌊
n
T
⌋
⌊
m
T
⌋
≡
u
(
T
)
⌊
n
T
⌋
⌊
m
T
⌋
m
o
d
1
e
9
+
6
m
o
d
1
e
9
+
7
u(T)^{{\lfloor\frac{n}{T}\rfloor}{\lfloor\frac{m}{T}\rfloor}}\equiv u(T)^{{\lfloor\frac{n}{T}\rfloor}{\lfloor\frac{m}{T}\rfloor}\space mod \space 1e9+6}\space mod\space 1e9+7
u(T)⌊Tn⌋⌊Tm⌋≡u(T)⌊Tn⌋⌊Tm⌋ mod 1e9+6 mod 1e9+7 ,可以对算法起到一点加速作用。
下面是AC代码。
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;
#define debug(x) cerr<<#x<<":"<<x<<endl
#define debug2(x,y) cerr<<#x<<":"<<x<<" "<<#y<<":"<<y<<endl
#define debug3(x,y,z) cerr<<#x<<":"<<x<<" "<<#y<<":"<<y<<" "<<#z<<":"<<z<<endl
typedef long long ll;
struct fastio{
fastio(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
}fio;
ll N;
const ll M=1e9+7;
const ll PHIM=M-1;
ll qpow(ll x,ll y,ll mod){
ll res=1;
x%=mod;
while(y){
if(y&1) res=(res*x)%mod;
x=(x*x)%mod;
y>>=1;
}
return res;
}
vector<int> prime,part,mu,f,inv_M,u,pu;
ll inv(ll x){
x%=M;
if(x<N) return inv_M[x];
return ((ll)(M-(M/x))*(ll)inv(M%x))%M;
}
void init(){
inv_M=f=part=vector<int>(N,0);
u=pu=vector<int>(N,1);
mu=vector<int>(N,-1);
inv_M[1]=f[1]=mu[1]=1;
for(ll i=2;i<N;++i){
f[i]=((ll)f[i-1]+(ll)f[i-2])%M;
inv_M[i]=((ll)(M-(M/i))*(ll)inv_M[M%i])%M;
if(!part[i]){
prime.push_back(i);
part[i]=i;
}
for(auto j:prime){
if(i*j>=N) break;
part[i*j]=j;
if(i%j==0){
mu[i*j]=0;
break;
}
else mu[i*j]=-mu[i];
}
}
for(int i=2;i<N;++i){
for(int j=i;j<N;j+=i){
if(mu[j/i]==-1) u[j]=(ll)u[j]*inv(f[i])%M;
if(mu[j/i]==1) u[j]=(ll)u[j]*f[i]%M;
}
pu[i]=(ll)u[i]*pu[i-1]%M;
}
}
ll g(ll x,ll y){
if(x>y) swap(x,y);
ll res=1;
for(ll l=3,r;l<=x;l=r+1){
r=min(x/(x/l),y/(y/l));
ll t=qpow((pu[r]*inv(pu[l-1])),((x/l)*(y/l))%PHIM,M);
res=res*t%M;
}
return res;
}
signed main(){
int t,n,m,maxx=-1;
cin>>t;
vector<pair<int,int> >data;
while(t--){
cin>>n>>m;
data.push_back({n,m});
maxx=max(maxx,max(n,m));
}
N=maxx;
init();
for(auto i:data) cout<<g(i.first,i.second)<<endl;
}
第一次做出洛谷黑题,用了一天时间,可喜可贺。。。个人感觉
u
(
x
)
u(x)
u(x) 也可以用线性筛预处理,可惜本蒟蒻想不出来。。。