$[ Luogu 4917 ] $天守閣的地板
\(\\\)
\(Description\)
定義二元函數\(F(x,y)\)表示,用\(x\times y\)的矩形不可旋轉的鋪成一個任意邊長的正方形,所需要的最少的矩形個數。
現在\(T\)組詢問,每次給出一個\(N\),求\(\prod_{i=1}^N\prod_{j=1}^N F(i,j)\) 模\(19260817\)的值。
- \(N\in[1,10^6],T\in[1,10^3]\)
\(\\\)
下面的表述中用\([x,y]\)表示\(Lcm(x,y)\),用\((x,y)\)表示\(Gcd(x,y)\)。
\(\\\)
\(Solution1:\text O(N+T\sqrt NlogN)\)
出題人的做法。對於\(N\)更大一些,\(T\)較小的情況表現比較優秀。
考慮一個邊長為\(x\times y\)的矩形鋪成的最小正方形的邊長,因為不可旋轉,所以答案為\([x,y]\)。
那麽用這種矩形鋪出這個正方形需要的塊數就是\(\frac{[x,y]^2}{xy}\),總面積除以單塊面積嘛。
然後所求可以形式化的寫出:
\[
\prod_{i=1}^N\prod_{j=1}^N\frac{[i,j]^2}{ij}=\prod_{i=1}^N\prod_{j=1}^N\frac{(\frac{ij}{(i,j)})^2}{ij}=\prod_{i=1}^N\prod_{j=1}^N\frac{ij}{(i,j)^2}=\frac{\prod_{i=1}^N\prod_{j=1}^Nij}{\prod_{i=1}^N\prod_{j=1}^N(i,j)^2}
\]
上面的轉化是基於以下定理:
\[ i\times j=(i,j)\times[i,j] \]
然後考慮對於每一次詢問的\(N\),如何快速求出答案。
\(\\\)
首先將分母分子分開考慮,對於分子,有:
\[
\prod_{i=1}^N\prod_{j=1}^Nij=\prod_{i=1}^Ni^N\times N\ !=(N\ !)^{2N}
\]
具體證明不太好說,大致是每一次循環到的\(i\)都要被乘上\(N\)次,手玩一下差不多就懂了。
這部分顯然可以\(\text O(N)\)求\(N!\),然後再每一個數都算一個快速冪即可,預處理復雜度\(\text O(Nlog(2N))\)。
\(\\\)
然後考慮分子部分如何求,首先先將平方提出來:
\[
\prod_{i=1}^N\prod_{j=1}^N(i,j)^2=\bigg(\prod_{i=1}^N\prod_{j=1}^N(i,j)\bigg)^2
\]
因為是連乘顯然合理。
然後括號裏的東西顯然可以根據\(Gcd\)的值分情況討論:
\[
\prod_{i=1}^N\prod_{j=1}^N(i,j)=\prod_{d=1}^N d^{\sum_{i=1}^N\sum_{j=1}^N[(i,j)=d]}=\prod_{d=1}^N d^{\sum_{i=1}^{\lfloor\frac Nd\rfloor}\sum_{j=1}^{\lfloor\frac Nd\rfloor}[(i,j)=1]}
\]
然後根據儀仗隊的解法,我們知道有:
\[
\sum_{i=1}^{\lfloor\frac Nd\rfloor}\sum_{j=1}^{\lfloor\frac Nd\rfloor}[(i,j)=1]=\bigg(\sum_{i=1}^{\lfloor \frac Nd\rfloor}\varphi(i)\bigg)\times 2-1
\]
然後可以線性篩求\(\varphi\),預處理\(\varphi\)的前綴和,查詢這個指數就是\(\text O(1)\)的了,預處理復雜度\(\text O(N)\)。
我們設\(g[x]\)表示:
\[
g[x]=\sum_{i=1}^{x}\sum_{j=1}^{x}[(i,j)=1]=\bigg(\sum_{i=1}^{x}\varphi(i)\bigg)\times 2-1
\]
那麽現在需要解決的式子是
\[
\prod_{i=1}^N\prod_{j=1}^N(i,j)=\prod_{d=1}^N d^{\ g[\lfloor\frac Nd \rfloor]}
\]
然後註意到這個東西可以除法分塊,考慮對於一個\(\lfloor\frac Nd\rfloor\)相同的區間\([L,R]\),答案為
\[
\prod_{i=L}^R i^{\ g[\lfloor\frac NL\rfloor]}=\bigg(\prod_{i=1}^N i\bigg)^{g[\lfloor\frac NL\rfloor]}=\bigg(\frac{R\ !}{(L-1)\ !}\bigg)^{g[\lfloor\frac NL\rfloor]}
\]
然後連乘積部分可以用階乘相除的方法求解,因為模意義下我們還需要處理每一個階乘的逆元。
然後快速冪求一下,在套回去平方一下,再求個逆元乘上\((N\ !)^{2N}\)就是答案了。
單次處理除法分塊+快速冪復雜度\(\text O(\sqrt NlogN)\),總復雜度\(\text O(N+T\sqrt NlogN)\)
\(\\\)
\(Code1:\text O(N+T\sqrt NlogN)\)
#include<cmath>
#include<cctype>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define R register
#define gc getchar
#define N 1000010
#define mod 19260817ll
using namespace std;
typedef long long ll;
ll g[N]={1,1},phi[N]={0,1},prm[N];
ll fac[N]={0,1},fac2[N]={0,1},inv[N]={1};
inline int rd(){
int x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
inline ll qpow(ll x,ll t){
ll res=1ll;
while(t){
if(t&1) (res*=x)%=mod;
(x*=x)%=mod; t>>=1;
}
return res;
}
inline void init(){
for(R int i=2;i<N;++i){
g[i]=1;
if(!phi[i]){phi[i]=i-1;prm[++prm[0]]=i;}
for(R int j=1,k;j<=prm[0]&&(k=prm[j]*i)<N;++j)
if(i%prm[j]) phi[k]=phi[i]*phi[prm[j]];
else{phi[k]=phi[i]*prm[j];break;}
}
for(R ll i=2;i<N;++i) fac[i]=fac[i-1]*i%mod,phi[i]+=phi[i-1];
for(R int i=2;i<N;++i) fac2[i]=qpow(fac[i],(i<<1));
for(R int i=1;i<N;++i) inv[i]=qpow(fac[i],mod-2);
}
inline void work(){
ll n=rd(),ans=1ll;
for(R ll l=1,t,r;l<=n;l=r+1){
t=n/l; r=n/t;
(ans*=qpow(fac[r]*inv[l-1]%mod,phi[t]*2-1))%=mod;
}
ans=(qpow(ans,(mod-2)<<1)*fac2[n])%mod;
printf("%lld\n",ans);
}
int main(){
init();
int t=rd();
while(t--) work();
return 0;
}
\(\\\)
\(Solution2:\text O(N\sqrt NlogN+T)\)
我的做法。卡著時限也就過了,對\(T\)大的時候表現會很優秀。
繼續考慮這個式子:
\[
\prod_{i=1}^N\prod_{j=1}^N\frac{[i,j]^2}{ij}=\prod_{i=1}^N\prod_{j=1}^N\frac{(\frac{ij}{(i,j)})^2}{ij}=\prod_{i=1}^N\prod_{j=1}^N\frac{ij}{(i,j)^2}=\frac{\prod_{i=1}^N\prod_{j=1}^Nij}{\prod_{i=1}^N\prod_{j=1}^N(i,j)^2}
\]
分子部分不變,還是那麽預處理,分母我有一個不同的做法。
首先還是把平方套在外面:
\[
\prod_{i=1}^N\prod_{j=1}^N(i,j)^2=\bigg(\prod_{i=1}^N\prod_{j=1}^N(i,j)\bigg)^2
\]
然後平方裏面的部分解法就不太一樣了,設一個\(g[n]\)表示:
\[
g[n]=\prod_{i=1}^n(i,n)=\prod_{d=1}^nd^{\ \varphi(\lfloor\frac{N}{d}\rfloor)}
\]
第一個等號是定義,第二個等號理解為,設\((i,n)=d\),那麽這樣的數對有\(\varphi(\lfloor\frac{N}{d}\rfloor)\)個,因為除掉\(d\)兩數應該互質。
然後類似儀仗隊的做法,把求和擴展到求積,那麽考慮將下三角矩陣的積求出來,答案是
\[
\prod_{i=1}^Ng[i]
\]
然後整個矩陣的積是下三角的平方除以對角線,對角線的積是\(\prod(i,i)=N\ !\),所以整個矩陣的積等於
\[
\frac{(\prod_{i=1}^Ng[i])^2}{N\ !}
\]
回到所求,有
\[
\frac{\prod_{i=1}^N\prod_{j=1}^Nij}{\prod_{i=1}^N\prod_{j=1}^N(i,j)^2}=\frac{(N\ !)^{2N}}{\bigg(\frac{(\prod_{i=1}^Ng[i])^2}{N\ !}\bigg)^2}=\frac{(N\ !)^{2N+2}}{(\prod_{i=1}^Ng[i])^4}
\]
復雜度預處理 \(g\) 數組\(\text O(N\sqrt NlogN)\),分別代表質因數分解和快速冪的復雜度。
其他線性篩、求階乘、求逆元等的復雜度都不會太高,之後顯然每一個數的答案都可以\(\text O(1)\)得到了。
\(\\\)
\(Code2:\text O(N\sqrt NlogN+T)\)
#include<cmath>
#include<cctype>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define R register
#define gc getchar
#define N 1000010
#define mod 19260817ll
using namespace std;
typedef long long ll;
ll g[N]={1},fac[N]={0,1};
int phi[N]={0,1},prm[N];
inline int rd(){
int x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
inline ll qpow(ll x,ll t){
ll res=1ll;
while(t){
if(t&1) (res*=x)%=mod;
(x*=x)%=mod; t>>=1;
}
return res;
}
inline void init(){
for(R int i=2;i<N;++i){
if(!phi[i]){phi[i]=i-1;prm[++prm[0]]=i;}
for(R int j=1,k;j<=prm[0]&&(k=prm[j]*i)<N;++j)
if(i%prm[j]) phi[k]=phi[i]*phi[prm[j]];
else{phi[k]=phi[i]*prm[j];break;}
}
for(R ll i=2;i<N;++i) fac[i]=fac[i-1]*i%mod;
for(R int i=2;i<N;++i) fac[i]=qpow(fac[i],(i<<1)+2);
for(R int i=1,r;i<N;++i){
r=sqrt(i); g[i]=1ll;
for(R int j=1;j<=r;++j)
if(i%j==0){
(g[i]*=qpow(j,phi[i/j]))%=mod;
if(i/j!=j) (g[i]*=qpow(i/j,phi[j]))%=mod;
}
(g[i]*=g[i-1])%=mod;
}
for(R int i=1;i<N;++i) g[i]=qpow(g[i],(mod-2)*4);
for(R int i=1;i<N;++i) (g[i]*=fac[i])%=mod;
}
int main(){
init();
int t=rd();
while(t--) printf("%lld\n",g[rd()]);
return 0;
}
\(\\\)
\(Solution3:\text O(N\times lnN\times logN+T)\)
優化一下第二個做法。
依次考慮每一個數對每一個 \(g\) 的貢獻,所以一個數 \(i\) 能做貢獻的數有\(\lfloor\frac Ni\rfloor\)個,直接暴力乘更新 \(g\) 就好。
此時復雜度神奇的降到了調和級數,也就是\(ln N\)。
\(\\\)
\(Code3:\text O(N\times lnN\times logN+T)\)
#include<cmath>
#include<cctype>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define R register
#define gc getchar
#define N 1000010
#define mod 19260817ll
using namespace std;
typedef long long ll;
ll g[N]={1,1},fac[N]={0,1};
int phi[N]={0,1},prm[N];
inline int rd(){
int x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
inline ll qpow(ll x,ll t){
ll res=1ll;
while(t){
if(t&1) (res*=x)%=mod;
(x*=x)%=mod; t>>=1;
}
return res;
}
inline void init(){
for(R int i=2;i<N;++i){
g[i]=1;
if(!phi[i]){phi[i]=i-1;prm[++prm[0]]=i;}
for(R int j=1,k;j<=prm[0]&&(k=prm[j]*i)<N;++j)
if(i%prm[j]) phi[k]=phi[i]*phi[prm[j]];
else{phi[k]=phi[i]*prm[j];break;}
}
for(R ll i=2;i<N;++i) fac[i]=fac[i-1]*i%mod;
for(R int i=2;i<N;++i) fac[i]=qpow(fac[i],(i<<1)+2);
for(R int i=1;i<N;++i)
for(R ll j=1;j*(ll)i<(ll)N;++j) (g[j*i]*=qpow(i,phi[j]))%=mod;
for(R int i=1;i<N;++i) (g[i]*=g[i-1])%=mod;
for(R int i=1;i<N;++i) g[i]=qpow(g[i],(mod-2)*4);
for(R int i=1;i<N;++i) (g[i]*=fac[i])%=mod;
}
int main(){
init();
int t=rd();
while(t--) printf("%lld\n",g[rd()]);
return 0;
}
$[\ Luogu\ 4917\ ]\ $天守閣的地板