题解: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))\)。

浙公网安备 33010602011771号