【洛谷】P8792 [蓝桥杯 2022 国 A] 最大公约数 的题解

【洛谷】P8792 [蓝桥杯 2022 国 A] 最大公约数 的题解

题目传送门

思路

首先先考虑数组中是否存在 1 1 1,如果数组中存在 1 1 1,那么我们可以直接进行平铺把全部变成 1 1 1,假设 1 1 1 的个数为 x x x 个,那么最终的答案应该是 n − x n-x nx 次。

如果原数组中不存在 1 1 1,该如何呢?那么我们应该想方法变出一个 1 1 1,然后使用这个 1 1 1 进行平推将数组全部变成 1 1 1。关于 gcd ⁡ \gcd gcd,我们首先要明白——如果一段子数组的的 gcd ⁡ \gcd gcd 1 1 1,那么原数组的 gcd ⁡ \gcd gcd 也一定为 1 1 1。 这也非常容易理解,如果存在一个数组的 gcd ⁡ \gcd gcd 1 1 1,那么这个数组无论再加上任何正整数, gcd ⁡ \gcd gcd 也永远是 1 1 1,因为 1 1 1 和任何数的 gcd ⁡ \gcd gcd 都是 1 1 1

题意要求最少次数,那么在没有 1 1 1 的情况下,我们需要使用最少的步数获得 1 1 1 ,那么就是我们需要在数组中找到最短的子数组,使得它们的 gcd ⁡ \gcd gcd 1 1 1。所以我们会涉及道查询区间 gcd ⁡ \gcd gcd 这个操作,直接暴力肯定不可取,所以我们应该联想到线段树来进行查询。

那么如何寻找最短的子数组满足区间 gcd ⁡ \gcd gcd 1 1 1 呢?我们可以考虑二分。对于数组中的每个数我们都可以固定为左端点 1 1 1,然后去二分它的右端点,求出使得区间 [ l , r ] [l,r] [l,r] gcd ⁡ \gcd gcd 1 1 1 的最小的右端点。既然二分就需要满足二段性,根据上一段的描述,我们可以知道,如果 [ l , r ] [l,r] [l,r] gcd ⁡ \gcd gcd 1 1 1,那么 [ l , r + 1 ] … [ l , n ] [l,r+1] \dots [l,n] [l,r+1][l,n] 这些区间的 gcd ⁡ \gcd gcd 也一定为 1 1 1,而 [ l , l + 1 ] … [ l , r − 1 ] [l,l+1] \dots [l,r−1] [l,l+1][l,r1] 这些区间却并不一定符合条件。这样每个数我们都定为左端点去二分它的右端点,所有答案取最小值就能找出 gcd ⁡ \gcd gcd 1 1 1 的最短区间。

当然我们如何判断无解的情况呢?还是根据上述 gcd ⁡ \gcd gcd 的理论知识,如果 [ l , n ] [l,n] [l,n] gcd ⁡ \gcd gcd 都不为 1 1 1,那么任何子数组的 gcd ⁡ \gcd gcd 也不可能为 1 1 1,此时为无解。

代码

#include <bits/stdc++.h>
#define endl "\n"
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
namespace fastIO {
	inline int read() {
		register int x = 0, f = 1;
		register char c = getchar();
		while (c < '0' || c > '9') {
			if(c == '-') f = -1;
			c = getchar();
		}
		while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
		return x * f;
	}
	inline void write(int x) {
		if(x < 0) putchar('-'), x = -x;
		if(x > 9) write(x / 10);
		putchar(x % 10 + '0');
		return;
	}
}
using namespace fastIO;
int a[100005];
struct node {
	int l, r, g;
}
tr[100005 << 2];
void pushup(int k) {
	tr[k].g = __gcd(tr[k * 2].g, tr[k * 2 + 1].g);
}
void build(int k, int l, int r) {
	tr[k].l = l, tr[k].r = r;
	if (l == r) {
		tr[k].g = a[l];
		return;
	}
	int mid = l + r >> 1;
	build(k * 2, l, mid);
	build(k * 2 + 1, mid + 1, r);
	pushup(k);
}
int query(int k, int L, int R) {
	if(tr[k].l == L && tr[k].r == R) {
		return tr[k].g;
	}
	int mid = (tr[k].l + tr[k].r) >> 1;
	if(R <= mid) {
		return query(k * 2, L, R); 
	}
	else if(L > mid) {
		return query(k * 2 + 1, L, R);
	}
	
	else {
		return __gcd(query(k * 2, L, mid), query(k * 2 + 1, mid + 1, R));
	}
}
int main() {
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout); 
	int n;
	n = read();
	int ct = 0;
	for (int i = 1; i <= n; i++) {
		a[i] = read();
		ct += a[i] == 1;
	}
	if(ct) { //有1直接平铺 
		write(n - ct);
		return 0;
	}
	build(1, 1, n);
	if(query(1, 1, n) != 1) {
		write(-1);
		return 0;
	}
	int min_step = 0x3f3f3f3f;
	for(int i = 1; i <= n; i ++) {
		int l = i + 1, r = n, res = 0x3f3f3f3f;
		while(l <= r) {
			int mid = l + r >> 1;
			if(query(1, i, mid) == 1) {
				res = mid - i;
				r = mid - 1;
			} 
			else {
				l = mid + 1;
			}
		}
		min_step = min(min_step, res);
	}
	write(min_step + n - 1);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值