P5655 基础数论函数练习题 题解
chen_03 Orz!
题意
给定长度为 \(n\) 的数组 \(a\),\(Q\) 次询问 \(\operatorname{lcm}\left(a_l, a_{l + 1}, \ldots , a_{r - 1}, a_r\right)\),对 \({10}^9 + 7\) 取模。
题解
对于每个右端点,我们维护每个左端点的答案。
具体的,设当前右端点为 \(m\),我们尝试找到数列 \(b_i\) 使得区间 \([i,m]\) 的答案为 \(\prod_{j=i}^{m}b_j\),显然 \(b_{i}|a_{i}\)。
假设我们已求出右端点为 \(m-1\) 时的数组 \(b\),现在尝试求出右端点为 \(m\) 时的数组 \(b'\)。
首先,\(b'_m=a_m\)。若 \(j\gt i\) 时 \(b'_j\) 都已求出,那么有:
两式相除得:
设 \(X=a_{m},Y=b_i,Z=\prod_{j=i+1}^{m-1}b_j\),则:
通过上述式子我们已经可以 \(O\left(Tn^2\log a\right)\) 求出答案。
设 \(s_i=\prod_{j=i}^{m-1}b_j\),注意到 \(\gcd\left(a_{m},\prod_{j=i+1}^{m-1}b_j\right)=\gcd\left(a_{m},s_{i+1}\right)\) 一定是 \(a_m\) 的约数,且对 \(\forall1\le i\lt j\le m\),有 \(\gcd\left(a_{m},s_j\right)|\gcd\left(a_{m},s_i\right)\),因此不同的 \(\gcd\left(a_{m},s_i\right)\) 取值只有 \(O\left(\log a\right)\) 种。
如何快速找到这 \(O\left(\log a\right)\) 个位置来减少计算 \(\gcd\) 的次数呢?注意到若 \(\gcd\left(a_{m},s_i\right)>\gcd\left(a_{m},s_{i+1}\right)\),那么一定存在某个质因子 \(p\) 在左式中的次数比右式中的高。
设 \(p\) 在 \(a_{m},s_i,s_{i+1}\) 中的出现次数分别为 \(x,y,z\),那么 \(y>z\),又因为 \(\min\left(x,y\right)>\min\left(x,z\right)\),因此 \(x>z\)。
故 \(\min\left(x,y\right)>z\),所以这等价于 \(\left(s_{i+1}\bmod a_m\right)\bmod \gcd\left(a_m,s_i\right)\ne0\)。
因此,用一个变量 \(\text{tmp}\) 存当前 \(\gcd\left(a_m,s_i\right)\) 的值,根据 \(\left(s_{i+1}\bmod a_m\right)\bmod\text{tmp}\) 是否等于 \(0\) 可判断出是否需要更新 \(\text{tmp}\)。
总时间复杂度 \(O\left(Tn\left(n+\log^2a\right)\right)\)。
Code
#include<bits/stdc++.h>
#define LL long long
#define mod 1000000007
using namespace std;
int n,q,Test_num;
LL tmp,c,tmp1,tmp2;
LL a[302],b[302],s[302];
LL ans[302][302];
inline LL gcd(LL a,LL b)
{
return b? gcd(b,a%b):a;
}
inline void solve()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
for(int i=1;i<=n;++i)
{
ans[i][i]=(b[i]=a[i])%mod,s[i]=1;
for(int j=i-1;j;--j)s[j]=((__int128)s[j+1]*b[j])%a[i];
tmp2=gcd(s[1],a[i]);
for(int j=1;j<i;++j)if(s[j+1]%tmp2)tmp1=gcd(s[j+1],a[i]),c=tmp2/tmp1,tmp2=tmp1,tmp/=c,b[j]/=c;
for(int j=i-1;j;--j)ans[j][i]=(ans[j+1][i]*(b[j]%mod))%mod;
}
for(int i=1,x,y;i<=q;++i)scanf("%d%d",&x,&y),printf("%lld\n",ans[x][y]);
}
int main()
{
for(scanf("%d",&Test_num);Test_num--;)solve();
return 0;
}