【洛谷P5655】基础数论函数练习题
数论+珂学卡常
题目大意:
多组数据,每组给定 $n$ 个数,多次询问一个区间内的最小公倍数。
题解:
我们对质因子 $p^k$ 的贡献进行考虑。我们只在 $p^k$ 最后出现的地方计算 $p$ 的贡献。
意思是说,我们每新插入一个数,其包含 $p,p^2,\dots,p^k$,那么我们令 $p^1\sim p^k$ 的贡献都计算在最后一个位置上,之前再出现 $p$ 的话,就表示 $p^{k+1},p^{k+2},\dots$,这样保证任意一个以当前的数结尾的区间的乘积恰好是最小公倍数,没有重复。
例如:数列 $p^5,\dots,p^3,\dots,p^2$ 的实际情况是 $p^2,1,1,\dots,p,1,1,\dots,p^2$。
本题的值域范围较大,对每个数分解质因数是不可行的。我们需要想办法整体计算贡献。
我们新加入一个 $a$ 的时候,和前面每个数考虑,依次取 $\gcd$,其值就是 $a$ 中和它重复的部分。我们把重复的部分在当前数中除掉,同时在 $a$ 中除掉(因为之前的数的贡献独立,除掉的部分不能和之前的再考虑),然后继续向前考虑(最终插入的还是原来的 $a$)。
这样插入后,以当前结点为结尾的区间的最小公倍数,就是当前存的每个数的乘积。
时间复杂度 $O(T(q+n^2\log a))$,需要在求 $\gcd$ 的时候加上珂学优化才可以通过。
Code:
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=305,md=1e9+7;
char buf[(int)1e8],*ss=buf;
inline int init(){buf[fread(buf,1,(int)1e8-1,stdin)]='\n';fclose(stdin);return 0;}
const int __START__=init();
inline LL readint(){
LL d=0;
while(*ss<'0')++ss;
while(*ss>='0')d=d*10+(*ss++^'0');
return d;
}
int n,T,q;
LL a[N];
int ans[N][N];
inline LL gcd(LL a, LL b){
if(!a||!b)return a|b;
register int t=__builtin_ctzll(a|b);
a>>=__builtin_ctzll(a);
do{
b>>=__builtin_ctzll(b) ;
if(a>b){LL t=b;b=a,a=t;}
b-=a;
}while(b);
return a<<t;
}
int main(){
for(T=readint();T--;){
n=readint(),q=readint();
for(register int r=1;r<=n;++r){
a[r]=readint();
LL x=a[r];
int*Ans=ans[r];
Ans[r]=x%md;
for(register int i=r-1;i;--i){
LL k=x==1?1:gcd(x,a[i]);
a[i]/=k,x/=k;
Ans[i]=Ans[i+1]*(LL)(a[i]%md)%md;
}
}
while(q--){
int l=readint(),r=readint();
printf("%d\n",ans[r][l]);
}
}
return 0;
}