题解:P1890 gcd 区间

题解都是用的 ST 表和线段树等方法。我来写一篇离线的解法。

思路分析

暴力很容易想到,但显然超时。

GCD 的单调不增

这个是可以离线操作的关键。

由题目可知对于一个固定左端点 \(l\),随着右端点 \(r\) 不断向右扩展,区间 GCD 是单调不增的:

\[\gcd(a_l) \geq \gcd(a_l, a_{l+1}) \geq \gcd(a_l, a_{l+1}, a_{l+2}) \geq \cdots \]

所以 如果我们已经算出了 \(\gcd(a_l, \dots, a_k)\),那么 \(\gcd(a_l, \dots, a_r)\)(\(r > k\))可以直接在此基础上继续与后面的数求 GCD,而不需要从头重新计算。

离线 + 排序优化暴力

之后就很简单了,查询按左端点排序,左端点相同则按右端点排序。这样,对于左端点相同的查询,右端点是递增的,避免重复计算。


Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[1009];
struct ask{
	int l,r,id,ans=0;
}p[1000009];
//排序
bool cmp1(ask x,ask y){
	if(x.l!=y.l){
		return x.l<y.l;
	}
	return x.r<y.r;
}
//还原
bool cmp2(ask x,ask y){
	return x.id<y.id;
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(nullptr);
    int n,m,ans=0,la=0;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
    	cin>>a[i];
	}
	for(int i=1;i<=m;i++){
		cin>>p[i].l>>p[i].r;
		p[i].id=i;//记录数的编号
	}
	sort(p+1,p+m+1,cmp1);
	for(int i=1;i<=m;i++){
    //左端点变了,需要重新从新的左端点开始
		if(i==1||p[i].l!=p[i-1].l){
			ans=a[p[i].l];
			la=p[i].l;
		}
    //从上次算到的位置继续往后扩展
		for(int j=la+1;j<=p[i].r;j++){
			ans=__gcd(ans,a[j]);
		}
		p[i].ans=ans;
		la=p[i].r;
	}
	sort(p+1,p+m+1,cmp2);//还原输出顺序
	for(int i=1;i<=m;i++){
		cout<<p[i].ans<<"\n";
	}
	return 0;
}

时间复杂度 \(\mathcal{O}(m \log m + (n + m) \log(\max a_i))\)。

posted @ 2026-09-27 16:21  Lexot  阅读(4)  评论(0)    收藏  举报