蓝桥杯2022年第十三届决赛真题-近似 GCD(C/C++组)
题目描述
小蓝有一个长度为 n 的数组 A = (a1, a2, · · · , an),数组的子数组被定义为从原数组中选出连续的一个或多个元素组成的数组。数组的最大公约数指的是数组中所有元素的最大公约数。如果最多更改数组中的一个元素之后,数组的最大公约数为 g,那么称 g 为这个数组的近似 GCD。一个数组的近似 GCD 可能有多种取值。
具体的,判断 g 是否为一个子数组的近似 GCD 如下:
-
如果这个子数组的最大公约数就是 g,那么说明 g 是其近似 GCD。
-
在修改这个子数组中的一个元素之后(可以改成想要的任何值),子数组的最大公约数为 g,那么说明 g 是这个子数组的近似 GCD。
小蓝想知道,数组 A 有多少个长度大于等于 2 的子数组满足近似 GCD 的值为 g。
输入格式
输入的第一行包含两个整数 n, g,用一个空格分隔,分别表示数组 A 的长度和 g 的值。
第二行包含 n 个整数 a1, a2, · · · , an,相邻两个整数之间用一个空格分隔。
输出格式
输出一行包含一个整数表示数组 A 有多少个长度大于等于 2 的子数组的近似 GCD 的值为 g 。
样例输入
复制
5 3
1 3 6 4 10
样例输出
复制
5
提示
满足条件的子数组有 5 个:
[1, 3]:将 1 修改为 3 后,这个子数组的最大公约数为 3 ,满足条件。
[1, 3, 6]:将 1 修改为 3 后,这个子数组的最大公约数为 3 ,满足条件。
[3, 6]:这个子数组的最大公约数就是 3 ,满足条件。
[3, 6, 4]:将 4 修改为 3 后,这个子数组的最大公约数为 3 ,满足条件。
[6, 4]:将 4 修改为 3 后,这个子数组的最大公约数为 3,满足条件。
对于 20% 的评测用例,2 ≤ n ≤ 102 ;
对于 40% 的评测用例,2 ≤ n ≤ 103;
对于所有评测用例,2 ≤ n ≤ 105 , 1 ≤ g, ai ≤ 109。
思路一
- 创建数组arr
- 遍历数组首先选出能够整除g的所有元素,打上标记
- 获取打上标记的数组元素索引组成的数组arr_true
- 使用数组arr的长度进行遍历,同时遍历arr_true数组,找出可能与arr_true数组中元素相连的子数组数量
#include<iostream>
using namespace std;
// 定义结构体,方便判断能否被gcd整除
struct num{
int value;
bool isreduce;
};
int countTrue(int arr[], int length){
int count = 0;
for (int i = 0; i < length; ++i) {
if (arr[i] == 1){
count++;
}
}
return count;
}
int main() {
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
int length;
int g;
cin >> length >> g;
int arr[length];
num arr1[length];
for (int i = 0; i < length; ++i) {// 接受输入
cin >> arr[i];
}
for (int i = 0; i < length; ++i) {
int temp = arr[i] % g;
if (temp != 0 ){ // 如果不能被整除
arr1[i].value = arr[i];
arr1[i].isreduce = false;
}
else{
arr1[i].value = arr[i];
arr1[i].isreduce = true;
}
}
// 分割成子数组,且子数组的元素数量大于等于2
// 或者获取num.isreduce组成的数组
for (int i = 0; i < length; ++i) {
arr[i] = arr1[i].isreduce;
}
//获取num中1的索引
int count = countTrue(arr,length);
int arr_true[count];
int temp = 0;
for (int i = 0; i < length; ++i) {
if(arr[i] == 1){
arr_true[temp] = i;
temp++;
}
}
// 数组长度为length
int result = 0;
for (int i = 0; i < length; ++i) {
int t = i;
for (int j = 0; j < count; ++j) {
if(arr_true[j] == t - 1){
continue;
}
if(arr_true[j] == t){
result++;
t++;
continue;
}
if(arr_true[j] == t + 1){
result++;
t++;
}
}
}
cout << result;
return 0;
}
结果
时间超限 22分
思路二
- 使用辗转相除法定义一个求最大公约数的函数gcd。
- 使用两个指针,用来指出前后的数组长度,该数组为包含一个不为g的倍数的子数组。
- 当第一个数满足条件时,但不满足数组长度为2,result不会增加。
- 当一直不满足条件时,需要一个标记last,区别于j,一直指向不满足条件的数,且为上一个满足条件的最长子数组的下一个不满足条件的数。
- 直到寻找到下一个满足条件的值时,将result + = i - j。
- 当下一个值不满足条件时,将i赋值给last,并将j = last + 1。
结果
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int n, g;
int a[N];
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
scanf("%d%d", &n, &g);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
int last = 0;
long long res = 0;
for (int i = 1, j = 1; i <= n; i ++ )
{
int t = gcd(g, a[i]);
if (t != g) j = last + 1, last = i;
if (i - j + 1 >= 2) res += i - j;
}
printf("%lld", res);
return 0;
}