P8792 [蓝桥杯 2022 国 A] 最大公约数

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

posted @ 2024-05-27 10:52  ltign  阅读(9)  评论(0编辑  收藏  举报