蓝桥杯真题——近似gcd(很离谱!!!)

目录

[题目链接:1.近似gcd - 蓝桥云课](#题目链接:1.近似gcd - 蓝桥云课)

题目描述

输入格式

输出格式

样例输入

样例输出

样例说明

评测用例规模与约定

解法一:我称之为逆天

Java写法:

AC情况

不是哥们儿

解法二:维护滑动窗口

Java写法:

AC情况

总结


题目链接:1.近似gcd - 蓝桥云课

注:下述题目描述和示例均来自蓝桥云客

题目描述

小蓝有一个长度为 n 的数组 A=(a1,a2,⋯,an), 数组的子数组被定义为从 原数组中选出连续的一个或多个元素组成的数组。数组的最大公约数指的是数 组中所有元素的最大公约数。如果最多更改数组中的一个元素之后, 数组的最 大公约数为 g, 那么称 g 为这个数组的近似 GCD。一个数组的近似 GCD 可能 有多种取值。

具体的, 判断 g 是否为一个子数组的近似 GCD 如下:

  1. 如果这个子数组的最大公约数就是 g, 那么说明 g 是其近似 GCD。

  2. 在修改这个子数组中的一个元素之后 (可以改成想要的任何值), 子数 组的最大公约数为 g, 那么说明 g 是这个子数组的近似 GCD。

小蓝想知道, 数组 A 有多少个长度大于等于 2 的子数组满足近似 GCD 的 值为 g 。

输入格式

输入的第一行包含两个整数 n,g 用一个空格分隔, 分别表示数组 A 的长 度和 g 的值。

第二行包含 n 个整数 a1,a2,⋯,an 相邻两个整数之间用一个空格分隔。

输出格式

输出一行包含一个整数表示数组 A 有多少个长度大于等于 2 的子数组的近 似 GCD 的值为 gg 。

样例输入

复制代码
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≤![10^2](https://latex.csdn.net/eq) ; 对于 40% 的评测用例, 2≤n≤![10^3](https://latex.csdn.net/eq); 对于所有评测用例, 2≤n≤![10^5](https://latex.csdn.net/eq),1≤g,![a_{i}](https://latex.csdn.net/eq)≤![10^9](https://latex.csdn.net/eq)。 *** ** * ** *** ![](https://i-blog.csdnimg.cn/direct/6d73126a88364197811f7ade09cfc884.jpeg) ## 解法一:我称之为逆天 我们需要找出所有长度大于等于2的子数组,然后让这个子数组的近似GCD等于给定的g。那什么是近似GCD呢?就是说,最多可以修改其中一个元素,使得修改后的子数组的GCD等于g。这样的话,这个子数组的近似GCD就是g。那我们需要计算满足条件的子数组数量。 首先,我们要理解题意。比如,子数组中的元素,最多只能改一个元素,就能让整个子数组的GCD变成g,那么这个子数组就符合条件。那这个条件如何判断呢? 首先,一个子数组的近似GCD为g的条件可能是什么?可能的两种情况:要么这个子数组的原始GCD已经是g,在一个就是可以通过修改一个元素使得GCD变为g。 那我们需要遍历所有可能的子数组,检查每个子数组是否满足上述条件。但这样的话,时间复杂度会很高,因为子数组的数量是O(n²)级别的,对于较大的n来说可能无法处理,这样的方法肯定不行。所以我们就需要找到一种优化的方法。 那问题的关键就在于如何高效地计算满足条件的子数组数量。那有没有办法不用遍历每个子数组呢? 我们需要明确:当子数组的近似GCD是g时,意味着该子数组中最多有一个元素不被g整除。或者说,子数组中最多有一个元素不能整除g,或者即使有多个元素不整除g,但是它们可以被调整(比如替换其中一个元素为g的倍数),使得整个数组的GCD变成g。更准确的条件应该是:**子数组中必须满足至少所有元素都整除g,或者存在恰好一个元素不整除g**,这样我们修改那个元素为一个能被g整除的数,那么整个数组的GCD就可能变成g。 另一个角度,修改后的数组的GCD必须是g。那修改后的数组所有元素必须是g的倍数,并且它们的GCD是g。也就是说,修改后的数组中的每个元素都是g的倍数,并且整个数组的GCD恰好是g。 比如,假设我们有一个子数组,其中所有元素都是g的倍数,但它们的GCD是2g。这时,原来的GCD是2g,但题目要求的近似GCD是g。这时不满足条件,因为即使允许修改一个元素,比如将其中一个元素改成g,那么整个数组的GCD可能变成g。例如数组是\[2g, 2g\],修改其中一个为g,GCD变成g。 这个时候,这个子数组的近似GCD是g吗?是的。所以这时候,这个子数组应该被算作符合条件的。 这说明,我们需要的条件是:在修改最多一个元素之后,整个子数组的GCD是g。所以原来的子数组的GCD可能不是g,但修改之后可以变成g。 那么问题转化为:对于每个子数组,是否存在最多一个元素,使得修改该元素为某个值后,整个子数组的GCD等于g? 如何快速判断这一点?可能的思路是:对于每个子数组,统计其中无法被g整除的元素的数量。如果这个数量是0或者1又怎么办? 比如,假设子数组中的元素全能被g整除。此时,它们的GCD可能等于g或更大的数。例如,如果所有元素都是2g的倍数,那么它们的GCD至少是2g,所以此时的GCD不是g。这时候,这个子数组的近似GCD是否为g?比如,假设我们修改其中一个元素为g,那么整个数组的GCD将变为gcd(2g, 2g, ..., g) = g。所以这种情况下,这样的子数组应该被算作满足条件。 **所以,如果子数组中所有元素都是g的倍数,那么可以通过修改其中一个元素为g,使得整个数组的GCD变为g**。 那问题就简化为,所有满足以下条件的子数组: **子数组中,最多有一个元素不是g的倍数。**或者,更准确地说,子数组中,最多有一个元素无法被g整除,或者所有元素都被g整除但它们的GCD可以通过修改一个元素变为g。 这时候,如何处理两种情况: 1. **子数组中所有元素都是g的倍数。**此时,无论它们的GCD是多少,只要可以通过修改一个元素,使得整个数组的GCD变为g。比如,原来的GCD是kg,这时候是否可以通过修改其中一个元素为g,使得新的GCD变为g。例如,数组是\[2g, 2g\],原来的GCD是2g。修改其中一个元素为g,那么新数组的GCD久是g了。所以这种情况下,所有元素都是g的倍数的子数组,无论原来的GCD是否为g,都符合条件。 2. **子数组中恰好有一个元素不是g的倍数。**此时,可以修改这个元素为g的倍数,那么剩下的元素都是g的倍数。比如,假设子数组中有一个元素不是g的倍数,其他都是g的倍数。修改这个元素为某个g的倍数,例如g。此时整个数组的GCD是原数组中其他元素的GCD和g的GCD。如果原数组中的元素的GCD是g,则总GCD是g。否则,比如原数组的GCD是kg,那么总GCD是gcd(kg, g) = g。所以无论原数组中其他元素的GCD是否为g,修改那个非g倍数的元素为g后,总GCD是g。所以这种情况下,这样的子数组是符合条件的。 那么最后,综合以上两种情况,满足条件的子数组的条件是:**子数组中无法被g整除的元素的数量\<=1**。并且,当所有元素都能被g整除时,它们的GCD是否是g并不影响,因为可以通过修改一个元素为g来使得总GCD变为g。例如,原数组中所有元素都是2g,那么它们的GCD是2g。修改其中一个元素为g,那么总GCD是g。所以这样的子数组符合条件。 所以,结论是:只要子数组中无法被g整除的元素的数量\<=1,那么该子数组满足条件。此时,无论原来的GCD如何,都可以通过修改最多一个元素,使得总GCD为g。这样,这样的子数组都被视为近似GCD为g的子数组? 那这样的话,我们的问题可以转化为:**找出所有长度\>=2的且其中无法被g整除的元素的个数\<=1的,子数组。** 我们试着推导一下?看样例输入: > 样例输入是5 3,数组是1 3 6 4 10。 > > 各个元素能否被3整除? > > 1%3=1 →不行。 > > 3%3=0 →可以。 > > 6%3=0 →可以。 > > 4%3=1 →不行。 > > 10%3=1 →不行。 现在,找**出所有长度\>=2的子数组,其中无法被g整除的元素数量\<=1。** 比如,第一个子数组是\[1,3\]。无法被3整除的元素数量是1个(1),符合条件。所以该子数组符合条件。样例说明中的第一个例子确实被算作正确。 再看第二个子数组\[1,3,6\]。无法被3整除的元素是1,数量为1。符合条件。 第三个子数组\[3,6\],无法被整除的元素数量是0,符合条件。 第四个子数组\[3,6,4\]:无法被整除的是4,数量是1。符合条件。 第五个子数组\[6,4\],无法被整除的是4,数量是1。符合条件。 还有没有其他符合条件的子数组? 比如,子数组\[6,4,10\],无法被整除的是4和10,数量是2。超过1,所以不符合条件。所以总数是5,与样例输出一致。 这说明上述结论是正确的。那问题就转化为统计所有长度\>=2的子数组,其中无法被g整除的元素个数\<=1。 既然简化了问题。那么,现在的问题是,如何高效地统计这样的子数组的数量。那现在,如何高效地计算所有长度\>=2的子数组,其中无法被g整除的元素个数不超过1个? 这个问题可以转化为**滑动窗口**的问题吗? 比如,我们可以遍历数组中的每个元素,维护一个窗口,其中包含最多一个无法被g整除的元素。当窗口右端扩展时,如果新元素无法被整除,则计数器加1。如果计数器超过1,则移动左指针,直到计数器\<=1。然后,对于每个窗口的右端点,统计所有可能的左端点,使得窗口内的元素个数\>=2,并且无法被整除的数量\<=1。 所以滑动窗口中的不能被g整除的元素的数目不能超过1。当窗口满足条件时,统计其中所有长度\>=2的子数组的数量。 具体的思路是: 使用双指针left和right,维护一个窗口,其中非g倍数的数目\<=1。当窗口中的非g数目超过1时,移动左指针。 对于每个右指针的位置,找到最大的左指针的位置,使得窗口内非g倍数的数目\<=1。然后,对于这个窗口,计算其中所有长度\>=2的子数组的数量。 例如,当前窗口是\[left, right\],其中非g数目\<=1。那么,以right为右端点的所有可能的子数组是\[left, right\], \[left+1, right\], ..., \[right-1, right\]。其中,长度\>=2的子数组数量是 right - left。例如,如果窗口是\[0,3\],那么以3为右端点的子数组可能有 \[0,3\](长度4),但需要长度\>=2,所以从left到right的长度是3-0+1=4。但我们需要子数组的长度\>=2,所以符合条件的子数组数目是 (right - left) - 0 +1 -1 = right - left。例如,当窗口长度为k(k\>=2),则其中符合条件的子数组数目为 **k-1**。比如,窗口长度为2的话,符合数目是1(子数组本身)。窗口长度为3的话,数目是2(比如子数组\[start, end\], \[start+1, end\], \[start+2, end\],其中长度\>=2的有两个)。 因此,总的时间复杂度可以是O(n),通过滑动窗口的方式。 那如何实现? 1. **预处理数组,将每个元素转换为是否是g的倍数**。例如,创建一个数组bad,其中bad\[i\] = 0表示a\[i\]是g的倍数,1表示不是。 2. **使用滑动窗口,维护当前窗口\[left, right\]内的bad之和**。当sum超过1时,移动左指针,直到sum\<=1。 3. **计算可能的起始点left,使得\[left, right\]内的sum \<=1**。然后,该窗口内的以right结尾的子数组数目是max(0, right - left)。注意,因为子数组必须长度\>=2,所以起始点必须\<=right-1。所以,起始点的范围是\[left, right-1\],数目是 right - left. 4. 将所有这些数目**累加**起来,就是最终的结果。 例如,样例输入中的数组转换后的bad数组是: 原数组:1,3,6,4,10 → bad数组是1,0,0,1,1. 然后,滑动窗口的过程: right=0(bad\[0\]=1):sum=1。left=0。此时,right - left =0-0=0。因为子数组长度必须\>=2,所以不符合。 right=1(bad\[1\]=0):sum=1。left=0。right-left=1-0=1。所以符合一个。对应的子数组是\[0,1\],即原数组的前两个元素。符合条件。 right=2(bad=0):sum=1。left=0。对应的子数组是\[0,2\], \[1,2\]。原数组中的这三个元素分别是1,3,6。它们的bad之和是1(只有第一个元素是bad)。所以所有子数组的bad数目\<=1。例如,\[0,2\]的数目是1,符合条件。子数组长度是3,符合条件。所以贡献是2。这时候总共有1+2=3。诸如此类。 那这样,在样例输入中,各步骤的应该是: right=0:不符合,因为长度不够。 right=1:符合**一个**。即子数组\[0,1\]。 right=2:符合**两个**。即子数组\[0,2\]、\[1,2\]。其中,这三个元素的bad数目是1,所有以right=2结尾的子数组起始点可以是0或1。对应的子数组长度是3或2,都符合条件。 right=3:bad\[3\]=1,sum=2。此时需要移动左指针,直到sum\<=1。例如,当left移动到1时,sum变为0+0+1=1。此时,sum=1。此时,right=3,left=1。符合条件的数量是**两个**。对应的子数组是\[1,3\]、\[2,3\]。例如,这两个子数组对应的原数组是3,6,4 → bad数目是1(4),所以符合条件。所以总贡献现在为1+2+2=5。 然后,right=4:bad=1。sum现在1+1=2。所以移动left到2,此时sum=0+1+1=2。继续移动left到3,sum=1+1=2。继续移动left到4,sum=1。此时,窗口是\[4,4\]。sum=1。但是子数组长度必须\>=2。所以right=4时,left=4,不符合要求。所以总数还是5. 则所有的情况为\[0,1\],\[0,2\],\[1,2\],\[1,3\],\[2,3\]映射到\[1,3,6,4,10\]也就是\[1,3\],\[1,6\],\[3,6\],\[3,4\],\[6,4

看到这里是不是以为自己也是天才了

Java写法:

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 读取输入:数组长度n和目标近似GCD值g
        int n = scanner.nextInt();
        int g = scanner.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = scanner.nextInt();
        }

        // 预处理:生成bad数组,标记每个元素是否能被g整除
        // bad[i] = 1 表示该元素需要修改,否则为0
        int[] bad = new int[n];
        for (int i = 0; i < n; i++) {
            bad[i] = (a[i] % g != 0) ? 1 : 0;
        }

        int left = 0;       // 滑动窗口左边界
        int currentSum = 0; // 当前窗口内bad值的总和(即需要修改的元素数量)
        long result = 0;    // 统计符合条件的子数组总数

        // 滑动窗口遍历数组
        for (int right = 0; right < n; right++) {
            currentSum += bad[right]; // 将右指针指向的元素加入窗口

            // 当窗口内bad值超过1时,右移左指针直到满足条件
            while (currentSum > 1) {
                currentSum -= bad[left];
                left++;
            }

            // 计算以当前right结尾的符合条件的子数组数量
            // 窗口范围是[left, right],需要长度>=2,因此有效子数组数量为 right - left
            // 例如:窗口长度为3(right - left +1 =3)时,有2个长度>=2的子数组以right结尾
            result += Math.max(0, right - left);
        }

        // 输出结果
        System.out.println(result);
        scanner.close();
    }
}

AC情况

不是哥们儿

沟槽的蓝桥杯还是没有发现吗,我这个方法连基本的题目都没满足。

我的答案连续吗,沟槽的测试用例居然还是过了简直逆天。




解法二:维护滑动窗口

  • 滑动窗口维护 :当遇到无法被g整除的元素时,将左边界 left 移到上一个bad元素的下一个位置,确保窗口内最多一个bad元素。

  • 结果累加 :窗口内所有以 right 结尾的子数组数量为 right - left(因为长度≥2的子数组有 right - left 个)。

Java写法:

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        long g = scanner.nextLong();
        long result = 0;

        int lastBadPos = -1; // 上一个无法被g整除的元素位置
        int left = 0;        // 滑动窗口左边界

        // 1 3 6 4 10
        for (int right = 0; right < n; right++) {
            long num = scanner.nextLong();
            // 若当前元素无法被g整除,更新窗口左边界
            if (num % g != 0) {
                left = lastBadPos + 1; // 左边界移到上一个bad元素的下一位
                lastBadPos = right;    // 更新最后一个bad元素的位置
            }

            // 计算以right结尾的合法子数组数量
            // 子数组长度至少为2,即区间长度 >=2 (right - left >=1)
            if (right - left >= 1) {
                result += right - left;
            }
        }

        System.out.println(result);
        scanner.close();
    }
}

AC情况

但是,这个方法相比于第一个要更合理,但是它仍然给不出你完全契合测试用例的解释,只能勉强解释通,由于篇幅限制我就不再赘述,大家可以根据代码推一推。


总结

所以我说这个沟槽的蓝桥杯的这道题太离谱了铁铁,我根本不能接受。因为它真的找不到一个完美的解释,但是又可以逃脱测试用例的安排,通关。

相关推荐
Мартин.2 分钟前
[CISSP] [6] 密码学和对称密钥算法
算法·密码学
勤劳的进取家8 分钟前
贪心算法之Huffman编码
数据结构·人工智能·算法·数学建模·贪心算法·动态规划
石去皿17 分钟前
力扣hot100 61-70记录
c++·算法·leetcode·深度优先
晓纪同学39 分钟前
随性研究c++-智能指针
开发语言·c++·算法
程序员爱钓鱼1 小时前
Go 连接 Oracle 太麻烦?一文教你优雅搞定 GORM + Oracle 全流程!
后端·算法·go
xuanjiong1 小时前
纯个人整理,蓝桥杯使用的算法模板day4(图论 最小生成树问题),手打个人理解注释,超全面,且均已验证成功(附带详细手写“模拟流程图”,全网首个
算法·蓝桥杯·图论
lmy201211082 小时前
提高:图论:强连通分量 图的遍历
c++·算法·图论·强联通分量
人类群星闪耀时3 小时前
破解 N 皇后 II:位运算的高效艺术
python·算法·数学建模
Demons_kirit3 小时前
LeetCode 1863.找出所有子集的异或总和再求和
数据结构·算法·leetcode
竹下为生3 小时前
LeetCode --- 443周赛
算法·leetcode·职场和发展