【洛谷】P8792 [蓝桥杯 2022 国 A] 最大公约数 的题解
题目传送门
思路
首先先考虑数组中是否存在 1 1 1,如果数组中存在 1 1 1,那么我们可以直接进行平铺把全部变成 1 1 1,假设 1 1 1 的个数为 x x x 个,那么最终的答案应该是 n − x n-x n−x 次。
如果原数组中不存在 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,r−1] 这些区间却并不一定符合条件。这样每个数我们都定为左端点去二分它的右端点,所有答案取最小值就能找出 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;
}