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;
}
完