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

· · 题解

哭了,卡了两个小时常了,Kelin为什么这么nb啊明明我复杂度比他低还没他跑得快

现在我比他快了

首先化式子.设n\leq m,除法均为整除.

\begin{array}{lcc}\prod\limits_{i=1}^n\prod\limits_{j=1}^mf[gcd(i,j)]=\prod\limits_{d=1}^n\prod\limits_{i=1}^n\prod\limits_{j=1}^m(f[d])^{[gcd(i,j)]}\\=\prod\limits_{d=1}^n(f[d])^{\sum\limits_{i=1}^n\sum\limits_{j=1}^m[gcd(i,j)=d]}\\=\prod\limits_{d=1}^n(f[d])^{\sum\limits_{k=1}^\frac{n}{d}\mu(k)\frac{n}{kd}\frac{m}{kd}}\\=\prod\limits_{T=1}^n\left(\prod\limits_{d|T}(f[d])^{\mu(\frac{T}{d})}\right)^{\frac{n}{T}\frac{m}{T}}\end{array}

于是我们预处理\prod\limits_{d|T}(f[d])^{\mu(\frac{T}{d})}即可O(\sqrt{n}\log n)回答询问了

如何预处理呢?

绝大多数人都是枚举d,然后更新T

然而事实上我们有更好的做法.参考rqy的blog.

g_{i,n}=\prod\limits_{d|T,d\text{只含前i种质因子}}(f[\frac{T}{d}])^{\mu(d)}

inv_{i,n}=\dfrac{1}{g_{i,n}}=\prod\limits_{d|T,d\text{只含前i种质因子}}(f[\frac{T}{d}])^{-\mu(d)}

于是枚举p_i.

如果p_i\not|n,那么g_{i,n}=g_{i-1,n},inv_{i,n}=inv_{i-1,n};

否则,g_{i,n}=g_{i-1,n}\times\prod\limits_{dp_i|T}(f[\frac{\frac{T}{p_i}}{d}])^{\mu(dp_i)}=g_{i-1,n}\times\prod\limits_{dp_i|T}(f[\frac{\frac{T}{p_i}}{d}])^{-\mu(d)}=g_{i-1,n}\times inv_{i-1,\frac{n}{p_i}}

同理有inv_{i,n}=inv_{i-1,n}\times g_{i-1,\frac{n}{p_i}}

于是我们就可以枚举素数来更新了.滚动数组消掉第一维即可.

于是我们把跑g和跑inv得时间从n\log mod降到了n\log \log n

可是还有一个问题!ginv的初值分别是finv(f).inv(f)如果硬算还有n\log mod的复杂度,这远远大于刚才的复杂度.

于是我观摩了Kelin的代码冥思苦想一番之后想起了一种神仙的做法.

我们来考虑f的前缀积sf,以及invf的前缀积si.

那么我们有invf(n)=si(n)\times sf(n-1)以及si(n-1)=si(n)\times f(n)

于是只要知道了si(N)我们就可以O(n)递推出所有invf,而si(N)=sf(N)^{-1},跑一次逆元求出来即可.

这个技巧还有很多用处emm它可以线性求数论函数逆元.

于是预处理的复杂度只剩n\log \log n我就不信哪个神仙能线筛

```cpp #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #define mod 1000000007 using namespace std; const int N=1e6+5; int s[N],invs[N]; int m,n,T,p[N],prime[100000],mu[N],cnt,nn[1005],mm[1005],sf[N]; inline int qpower(int a,int b) { int ans=1;for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)ans=1ll*ans*a%mod;return ans; } void make(int n) { mu[1]=p[1]=invs[0]=s[1]=invs[1]=sf[1]=1; for(int i=2;i<=n;i++) { s[i]=(s[i-1]+s[i-2])%mod,sf[i]=1ll*sf[i-1]*s[i]%mod;if(!p[i])prime[++cnt]=i,mu[i]=-1; for(int j=1,x;j<=cnt&&(x=i*prime[j])<=n;j++) { p[x]=1;if(i%prime[j])mu[x]=-mu[i]; else break; } } int sis=qpower(sf[n],mod-2); for(int i=n;i>=2;i--)invs[i]=1ll*sis*sf[i-1]%mod,sis=1ll*sis*s[i]%mod; for(int i=1;i<=cnt;i++) { for(int jj=n/prime[i],j=jj*prime[i];jj>=1;jj--,j=jj*prime[i]) { s[j]=1LL*s[j]*invs[jj]%mod; invs[j]=1ll*invs[j]*s[jj]%mod; } } for(int i=2;i<=n;i++)s[i]=1ll*s[i]*s[i-1]%mod,invs[i]=1ll*invs[i]*invs[i-1]%mod;; } int main() { scanf("%d",&T);int maxn=0; for(int i=1;i<=T;i++)scanf("%d%d",nn+i,mm+i),maxn=max(maxn,min(nn[i],mm[i])); make(maxn); for(int TTT=1;TTT<=T;TTT++) { int n=nn[TTT],m=mm[TTT]; if(n>m)swap(n,m);int ans=1; int i=1,lt=sqrt(n); for(;i<=lt;i++) { ans=(1ll*ans*qpower(1ll*s[i]*invs[i-1]%mod,1ll*(n/i)*(m/i)%(mod-1)))%mod; } for(;i<=n;i=lt+1) { lt=min(n/(n/i),m/(m/i)); ans=(1ll*ans*qpower(1ll*s[lt]*invs[i-1]%mod,(n/i)*(m/i)))%mod; } printf("%d\n",ans); } } ``` ~~卡常是无止境的~~ ```