P8792 [蓝桥杯 2022 国 A] 最大公约数
一、问题简析
st表
+ 二分
思路
要使数列都变成 \(1\),首先数列中要有 \(1\)。因为题目要求是用两个数的 \(gcd\) 代替其中一个数,所以我们要找到一个区间 \([L, R]\),该区间的 \(gcd\) 等于 \(1\)。
证:
\[\begin{split}
a_{L+1} &= gcd(a_L, a_{L+1}) \neq 1 \\
a_{L+2} &= gcd(a_{L+1}, a_{L+2}) \neq 1 \\
&...\\
a_{R-1} &= gcd(a_{R-2}, a_{R-1}) \neq 1\\
a_{R} &= gcd(a_{R - 1}, a_R) = 1 \\
\\
\therefore gcd(&a_L,a_{L+1},...,a_R)=1
\end{split}
\]
因此,我们需要查询区间 \(gcd\)。因为不需要区间修改,所以选择 \(st\) 表。
设数列长度为 \(n\),数列中 \(1\) 的个数为 \(cnt\),使区间 \(gcd=1\) 的最小区间长度为 \(len\),则答案为
if cnt > 0
ans = n - cnt // 非1的个数,就是操作次数
else if cnt == 0
if not exit such len
ans = -1 // 不能使数列都为1
else if exit such len
ans = len + n - 2 // 需要len-1次操作获得第一个1,此时数列中非1元素个数为n-1
\(st\) 表
按照模板修改即可 st表
二分
本题另一个问题就是找到上述的 \(len\)。我们可以对数列长度 \(n\) 进行二分,找到最小的长度 \(len\),使得数列中存在长度为 \(len\) 的区间 \(gcd=1\)。
// 二分判断函数,长度为x的区间是否gcd为1
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;
}
// 找到len
void solve(void)
{
int L = 1, R = n, len;
while (L <= R)
{
int M = (L + R) >> 1;
if (check(M))
{
len = M;
R = M - 1;
}
else
L = M + 1;
}
}
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll quickin(void)
{
ll ret = 0;
bool flag = false;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-') flag = true;
ch = getchar();
}
while (ch >= '0' && ch <= '9' && ch != EOF)
{
ret = ret * 10 + ch - '0';
ch = getchar();
}
if (flag) ret = -ret;
return ret;
}
const int MAX = 1e5 + 5;
int st[MAX][30], lg2[MAX], pw2[30], n;
int cnt = 0; // 记录数列中1的个数
int gcd(int a, int b)
{
if (b == 0) return a;
return gcd(b, a % b);
}
void init(void)
{
lg2[1] = 0;
for (int i = 2; i <= n; ++i) lg2[i] = lg2[i >> 1] + 1;
pw2[0] = 1;
for (int i = 1; i < 30; ++i) pw2[i] = pw2[i - 1] << 1;
for (int i = 1; i <= n; ++i)
{
st[i][0] = quickin();
if (st[i][0] == 1) ++cnt; // 统计1的个数
}
for (int j = 1; j <= lg2[n]; ++j)
for (int i = 1; i + pw2[j] - 1 <= n; ++i)
st[i][j] = gcd(st[i][j - 1], st[i + pw2[j - 1]][j - 1]);
}
int query(int L, int R)
{
int k = lg2[R - L + 1];
return gcd(st[L][k], st[R - pw2[k] + 1][k]);
}
// 二分判断函数,长度为x的区间是否gcd为1
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 main()
{
#ifdef LOCAL
freopen("test.in", "r", stdin);
#endif
n = quickin();
init();
if (cnt > 0)
{
printf("%d\n", n - cnt);
return 0;
}
if (query(1, n) > 1) printf("-1\n");
else
{
// 对区间长度n二分,找到最小的区间长度,使gcd==1
int L = 1, R = n, ans = 1e8;
while (L <= R)
{
int M = (L + R) >> 1;
if (check(M))
{
ans = M;
R = M - 1;
}
else
L = M + 1;
}
printf("%d\n", ans + n - 2);
}
return 0;
}
完