[特殊字符] 蓝桥杯脑洞剧场:当质数在圆周跳双人舞,GCD在循环赛道玩极限——谁才是算法之王?

#王者杯·14天创作挑战营·第1期#
🔍 引言

        想象一下:一群数字手拉手围成圆圈跳华尔兹,只有‘质数和’的舞伴才能赢得掌声;另一群数字在循环跑道上赛跑,裁判GCD举着‘最大公约数’的标尺,寻找最均衡的冠军……
        这不是童话,而是蓝桥杯算法题中的真实剧情!今天,让我们用数学的望远镜算法的指挥棒,揭开这两场数字盛宴的终极秘密!


📖 正文
Part 1 | 蓝桥杯的数学乐园:当数字变成舞者与运动员

蓝桥杯的题目总能把冰冷的数字变成鲜活的角色。今天的两个问题,一个让数字在圆周上寻找“质数舞伴”,另一个让数字在循环跑道上挑战GCD极限——看似毫无关联,却共享数学规律与指针操作的双重基因。


Part 2 | 题目一:圆周质数对(数学+双指针)

💡 问题核心
将数组首尾相连成环,找到一种排列方式,使得相邻两数之和为质数的对数最多。例如 [2,3,4] 排列为 3-2-4,有两对质数和(3+2=5,4+3=7)。

🔑 算法思路解析

  1. 质数的魔法筛选

    • 预处理质数表,快速判断任意两数之和是否为质数。

    • 小技巧:若两数之和为2,则必为1+1,但1非质数,可直接排除。

  2. 排列的舞蹈编排

    • 图论视角:将每个数字视为节点,若两数之和为质数则连边,问题转化为寻找包含最多边的哈密顿回路。

    • 贪心试探:用双指针从两端向中心逼近,优先连接质数和对,动态调整排列。

  3. 复杂度妥协

    • 暴力回溯法时间复杂度为O(n!),需用剪枝或动态规划优化。

⚡ 思维亮点

  • 数学与图论的跨界融合:用质数判定为数字建立“社交关系”,问题秒变“朋友圈最大闭环”。

  • 双指针的灵动性:像导演调整舞者位置,局部优化带动全局最优。

#include <stdio.h>   // 标准输入输出库,提供printf等函数
#include <stdbool.h> // 布尔类型支持库,定义bool、true、false
#include <stdlib.h>  // 标准库,包含动态内存管理、退出函数等
#include <string.h>  // 字符串操作库,提供memcpy等函数

// 全局状态记录
int max_pairs = 0;         // 记录找到的最大质数对数量
int* best_perm = NULL;     // 指向最佳排列数组的指针
bool* is_prime = NULL;     // 质数筛表指针(标记数字是否为质数)
int max_sum = 0;           // 数组元素两两之和的最大值

/**
 * 埃拉托斯特尼筛法生成质数表
 * @param max_num 筛表上限(生成0~max_num的质数标记)
 */
void sieve(int max_num) {
    // 分配质数筛表内存(包含0到max_num共max_num+1个元素)
    is_prime = (bool*)malloc((max_num + 1) * sizeof(bool));
    // 初始化所有元素为true(假设都是质数)
    memset(is_prime, true, (max_num + 1) * sizeof(bool));
    // 手动标记0和1为非质数
    is_prime[0] = is_prime[1] = false;
    // 筛法核心:从2开始遍历到sqrt(max_num)
    for (int i = 2; i * i <= max_num; ++i) {
        if (is_prime[i]) { // 如果i是质数,标记其倍数为非质数
            // 从i²开始,以i为步长标记倍数
            for (int j = i * i; j <= max_num; j += i) {
                is_prime[j] = false;
            }
        }
    }
}

/**
 * 计算环形排列的质数对数量
 * @param arr 当前排列数组指针
 * @param n 数组长度
 * @return 质数对数量
 */
int count_pairs(int* arr, int n) {
    int cnt = 0; // 质数对计数器
    for (int i = 0; i < n; ++i) { // 遍历所有相邻对
        // 计算当前元素与下一个元素的环形和
        int sum = arr[i] + arr[(i + 1) % n];
        // 检查是否在筛表范围内且为质数
        if (sum <= max_sum && is_prime[sum]) ++cnt;
    }
    return cnt;
}

/**
 * 递归生成全排列并更新最优解
 * @param arr 当前排列数组指针
 * @param start 当前处理的起始位置
 * @param n 数组长度
 */
void permute(int* arr, int start, int n) {
    // 递归终止条件:完成一个排列
    if (start == n) {
        // 计算当前排列的质数对数
        int current = count_pairs(arr, n);
        // 如果优于当前最优解则更新
        if (current > max_pairs) {
            max_pairs = current; // 更新最大对数
            memcpy(best_perm, arr, n * sizeof(int)); // 保存排列
        }
        return;
    }

    // 全排列生成(带剪枝优化)
    for (int i = start; i < n; ++i) {
        // 交换当前元素与后续元素生成新排列
        int tmp = arr[start];
        arr[start] = arr[i];
        arr[i] = tmp;

        // 剪枝优化:检查前一对是否有效(start>0时)
        if (start > 0) {
            // 计算前一个元素与当前元素的组合
            int sum = arr[start - 1] + arr[start];
            // 如果和超过筛表范围或非质数,则跳过该分支
            if (sum > max_sum || !is_prime[sum]) {
                // 回溯交换(恢复数组状态)
                arr[i] = arr[start];
                arr[start] = tmp;
                continue; // 跳过后续递归
            }
        }

        // 递归处理下一个位置
        permute(arr, start + 1, n);

        // 回溯恢复数组原始状态
        tmp = arr[start];
        arr[start] = arr[i];
        arr[i] = tmp;
    }
}

int main() {
    // 输入测试数据
    int arr[] = {2, 3, 4};
    int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度

    // 打印原始数组
    int length = sizeof(arr) / sizeof(arr[0]); // 获取数组长度
    printf("["); // 输出左括号
    for (int i = 0; i < length; i++) {
        // 控制逗号格式:第一个元素前不加逗号
        printf(i == 0 ? "%d" : ", %d", arr[i]);
    }
    printf("]\n"); // 输出右括号和换行

    max_sum = 0;
    for (int i = 0; i < n; ++i) {       // 外层遍历元素
        for (int j = 0; j < n; ++j) {   // 内层遍历元素
            if (i != j) {               // 排除相同元素组合
                int s = arr[i] + arr[j];
                if (s > max_sum) max_sum = s; // 更新最大值
            }
        }
    }

    // 初始化质数筛表(覆盖所有可能的和)
    sieve(max_sum);

    // 初始化最佳排列数组(分配内存并拷贝初始数组)
    best_perm = (int*)malloc(n * sizeof(int));
    memcpy(best_perm, arr, n * sizeof(int));

    // 创建排列生成缓冲区(避免修改原数组)
    int* buffer = (int*)malloc(n * sizeof(int));
    memcpy(buffer, arr, n * sizeof(int));

    // 递归生成所有排列并寻找最优解
    permute(buffer, 0, n);

    // 输出结果
    printf("Maximum Prime Logarithm: %d\n Arrangement Scheme:", max_pairs);
    for (int i = 0; i < n; ++i) { // 遍历最佳排列
        printf("%d ", best_perm[i]);
    }
    // 输出相邻和验证信息
    printf("\n Adjacency and Verification:\n");
    for (int i = 0; i < n; ++i) {
        int j = (i + 1) % n; // 环形下一个位置
        int sum = best_perm[i] + best_perm[j];
        printf("%d+%d=%d %s\n", best_perm[i], best_perm[j], sum,
               is_prime[sum] ? "prime" : "non-prime"); // 三目运算符选择输出
    }

    // 释放动态分配的内存
    free(is_prime);    // 释放质数筛表
    free(best_perm);   // 释放最佳排列数组
    free(buffer);      // 释放排列缓冲区
    return 0;          // 程序正常退出
}

输出结果:

 


Part 3 | 题目二:循环移位GCD极值(数学+滑动窗口)

💡 问题核心
对数组循环右移k次后,计算所有前缀GCD的最大值,最终取所有k中该最大值的最小可能值。例如 [8,6,12] 在k=1时前缀GCD最大值为12,需找到所有k对应结果中的最小值。

🔑 算法思路解析

  1. GCD的接力赛规则

    • 前缀GCD具有单调不增性:一旦GCD变小,后续前缀无法再变大。

  2. 滑动窗口的智慧

    • 将循环数组展开为双倍长度(如 [8,6,12,8,6,12]),用滑动窗口遍历所有可能的循环起点。

    • 动态维护当前窗口的前缀GCD,记录最小值。

  3. 数学性质加速

    • 若某前缀GCD降为1,可直接终止当前窗口计算(因1是最小可能值)。

⚡ 思维亮点

  • 循环变线性的魔术:通过数组翻倍将环形问题“拉直”,滑动窗口化身“时空管理者”。

  • GCD的单调性:利用数学性质大幅减少无效计算,效率提升如“快进键”。

#include <stdio.h>   // 标准输入输出库,提供printf函数
#include <limits.h>  // 定义整数类型极限值,如INT_MAX

/**
 * 寻找数组最小值
 * @param arr 数组指针(需检查非空)
 * @param n 数组长度(需大于0)
 * @return 数组中的最小值,空数组返回INT_MAX
 */
int findMin(int* arr, int n) {
    int min_val = INT_MAX; // 初始化为最大整数值(2147483647)
    for (int i = 0; i < n; ++i) { // 遍历数组每个元素
        if (arr[i] < min_val) {   // 发现更小值时触发更新
            min_val = arr[i];     // 更新当前最小值记录
        }
    }
    return min_val; // 返回遍历后得到的最小值
}

int main() {
    // 测试用例数组(原始数据)
    int arr[] = {8, 6, 12};
    // 计算数组长度:总字节数 / 单个元素字节数
    int n = sizeof(arr) / sizeof(arr[0]);

    // 打印原始数组(新增代码段)
    int length = sizeof(arr) / sizeof(arr[0]); // 获取数组长度(等同于n)
    printf("["); // 输出左方括号
    for (int i = 0; i < length; i++) { // 遍历数组元素
        // 条件运算符控制输出格式:首元素无前导逗号
        printf(i == 0 ? "%d" : ", %d", arr[i]);
    }
    printf("]\n"); // 输出右方括号和换行符

    // 调用函数获取结果
    int result = findMin(arr, n);

    // 格式化输出最终结果(修改后的输出语句)
    printf("The minimum value of the maximum GCD in all cyclic permutations: %d\n", result); // 输出结果
    return 0;
}

 

输出结果:

 


Part 4 | 双题对比:数学的浪漫与算法的冷峻
对比维度圆周质数对(数学+双指针)循环移位GCD极值(数学+滑动窗口)
核心目标最大化浪漫的“质数舞伴”对数最小化冷酷的“GCD最大值”
算法武器图论建模+贪心试探滑动窗口+GCD单调性
时间复杂度O(n²) ~ O(n!)(依赖优化)O(n²) → O(n)(优化后)
关键挑战哈密顿回路的边数最大化循环窗口的前缀GCD快速计算
思维美学艺术性编排(如舞蹈设计)工程性优化(如机械传动)

Part 5 | 总结:数学是诗,算法是剑

两道题宛如算法世界的“冰与火之歌”:

  • 圆周质数对是数学的浪漫诗篇,用质数为数字赋予情感,双指针是舞步的指挥家;

  • 循环GCD极值是算法的冷酷利剑,用滑动窗口切割问题,GCD单调性是斩断冗余的刃光。
    蓝桥杯的江湖中,唯有同时掌握“诗人的灵感”与“剑客的精准”,才能笑傲题海!


📝 结语

        数字的圆周上,质数对在跳着永恒的华尔兹;循环的赛道中,GCD在追逐极限的平衡点。算法之美,正在于将数学的抽象化为逻辑的具象,让无序变为有序。
        下一次,当你看到一串数字时,愿你能听见质数的圆舞曲,看见GCD的抛物线——因为每个数字,都是算法宇宙中等待被点亮的星辰。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

司铭鸿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值