P8792 题解
思路
为了使数组只剩 \(1\),需要从一个 \(1\) 开始不断与傍边的数做 gcd 操作,需要 \(n-cnt_1\) 次。
如果数组中没有 \(1\),那t_么需要连续对一段数 \([l,r]\) 做 gcd 操作得出一个 \(1\),再用一个 \(1\) 做 \(n-1\) 次操作覆盖数组。最后答案为 \(n-1+r-l+1-1\)。当 \([l,r]\) 区间的 gcd 为 \(1\) 时,可以选择该区间。
问题转换为:求一段最短的区间使其区间 gcd 为 \(1\)。
有 \(n^2\) 个区间,直接枚举计算复杂度是 \(O(n^3)\)。
由于没有修改,可以使用 ST 表 \(O(n\log n)\) 预处理,\(O(1)\) 得出区间的 gcd。复杂度 \(O(n^2)\)。
但不一定要枚举每个区间。如果最大的区间不可取,大区间下的小区间也不可取。可以用双指针弄到 \(O(n)\),但比较复杂。
询问最小,考虑二分答案,\(O(n\log n)\),可以接受。
code
int n,a[maxn],st[maxn][25];
int lg[maxn],pw[25];
int gcd(int a,int b){
if(a%b==0)return b;
return gcd(b,a%b);
}
int query(int l,int r){
int k=lg[r-l+1];
return gcd(st[l][k],st[r-pw[k]+1][k]);
}
int l,r,mid,ans;
bool check(int x){
for(int i=1;i+x-1<=n;i++){
int j=i+x-1;
if(query(i,j)==1)return true;
}
return false;
}
int cnt;
signed main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read(),st[i][0]=a[i],cnt+=(a[i]==1);
if(cnt==n){
printf("0\n");
return 0;
}
for(int i=1;i<=n;i++){
if(a[i]==1){
printf("%d\n",n-cnt);
return 0;
}
}
pw[0]=1;lg[1]=0;
for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
for(int i=1;i<=20;i++)pw[i]=pw[i-1]<<1;
for(int j=1;j<=lg[n];j++){
for(int i=1;i+pw[j]-1<=n;i++){
st[i][j]=gcd(st[i][j-1],st[i+pw[j-1]][j-1]);
}
}
if(query(1,n)!=1){
printf("-1\n");
return 0;
}
l=1,r=n;
while(l<=r){
mid=l+r>>1;
if(check(mid)){
ans=mid;
r=mid-1;
}
else l=mid+1;
}
printf("%d\n",ans-1+n-1);
}