P8792 题解

P8792

CF891A

思路

为了使数组只剩 \(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);
}
posted @ 2024-05-10 20:05  yhddd  阅读(14)  评论(0编辑  收藏  举报