LG P5655 基础数论函数练习题(GCD)

题目

题解:
考虑如何求 n n n个数的 l c m lcm lcm,可以求出前 n − 1 n-1 n1个数的 l c m = x lcm = x lcm=x,再和 a n = y a_n =y an=y l c m = x y g c d ( x , y ) lcm = \frac {xy}{gcd(x,y)} lcm=gcd(x,y)xy
然后就可以python骗到50分了
x y gcd ⁡ ( x , y ) = a n gcd ⁡ ( x , y ) x \frac {xy}{\gcd(x,y)} = \frac {a_n}{\gcd(x,y)} x gcd(x,y)xy=gcd(x,y)anx
x x x可以模。
考虑怎么在不用高精度的情况下计算 y gcd ⁡ ( x , y ) m o d    1000000007 \frac {y}{\gcd(x,y)} \mod 1000000007 gcd(x,y)ymod1000000007
gcd ⁡ ( x , y ) = gcd ⁡ ( x m o d    y , y ) \gcd(x,y) = \gcd(x \mod y , y) gcd(x,y)=gcd(xmody,y)
所以我们只需要再算 x m o d    y x\mod y xmody就行了。
可以 O ( n 2 + n log ⁡ n ) O(n^2+n\log n) O(n2+nlogn)求出 n n n个数的 l c m lcm lcm

考虑多组询问。
我们考虑预处理出所有答案。
假设我们安排了在求 l c m lcm lcm中一个一个加入 a i a_i ai的顺序,
那么放 a i a_i ai进来的时候,可以直接令 b i = a i gcd ⁡ ( a i , x ) b_i = \frac {a_i}{\gcd(a_i,x)} bi=gcd(ai,x)ai
那么答案就相当于是 ∏ b i \prod b_i bi
对于所有的区间,我们都安排一个统一的顺序(从大编号到小编号加入),那么我们可以寻求到一些可以重复利用的计算信息。
从小标号到大插入 a i a_i ai,然后对于每个 b j , j < i b_j,j<i bj,j<i更新为 b j g c d ( b j , ∏ k = j + 1 i b k ) \frac {b_j}{gcd(b_j,\prod _{k=j+1}^{i} b_k)} gcd(bj,k=j+1ibk)bj,其实只需要看 a i a_i ai就可以了。
然后(对于右端点是 i i i的)区间询问 [ l , i ] [l,i] [l,i]就是 ∏ j = l i b j \prod_{j=l}^ib_j j=libj
这个方法每次都要求 gcd ⁡ \gcd gcd,是 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn),总复杂度是 O ( T n 2 log ⁡ n ) O(Tn^2\log n) O(Tn2logn)难以通过。
O(1)gcd需要O(值域)的预处理

考虑优化每次求 gcd ⁡ \gcd gcd的过程。
一个数 a i a_i ai和前面 i − 1 i-1 i1 [ l , i − 1 ] [l,i-1] [l,i1]分别的 gcd ⁡ \gcd gcd只会有 O ( log ⁡ a ) O(\log a) O(loga)种。
所以从小编号到大编号更新 b j b_j bj,用模运算来快速判断 gcd ⁡ \gcd gcd是否会变小(一段变短乘积变小)以此计算。
复杂度是 O ( n 2 + n log ⁡ 2 n ) O(n^2 + n\log ^2n) O(n2+nlog2n)可以通过本题。

#include<bits/stdc++.h>
#define maxn 305
#define mod 1000000007
#define LL long long
using namespace std;

char cb[1<<15],*cs=cb,*ct=cb;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct)?0:*cs++)
template<class T>void read(T &res){
	char ch;
	for(;!isdigit(ch=getc()););
	for(res=ch-'0';isdigit(ch=getc());res=res*10+ch-'0');
}

int n,q,ans[maxn][maxn];
LL a[maxn],b[maxn];
inline LL mul(LL a,LL b,LL p){
	LL r = a*b-(LL)((long double)a/p*b+0.5)*p;
	return r < 0 ? r + p : r;
}
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(){
	int T;
	for(read(T);T--;){
		read(n),read(q);
		for(int i=1;i<=n;i++){
			read(a[i]);
			b[i] = 1;
			for(int j=i-1;j>=1;j--) b[j] = mul(a[j],b[j+1],a[i]);
			LL G = gcd(b[1] , a[i]) , t;
			for(int j=1;j<i;j++) 
				if(t=(b[j+1]%G)){
					t=gcd(t,G);
					a[j] /= G / t , G = t;
				}
			ans[i][i] = a[i] % mod;
			for(int j=i-1;j>=1;j--) ans[i][j] = 1ll * ans[i][j+1] * (a[j] % mod) % mod;
		}
		for(int l,r;q--;){
			read(l),read(r);
			printf("%d\n",(ans[r][l]+mod)%mod);
		}
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值