第十六届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
第十六届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

第十六届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

1、移动距离


(1)题目描述

小明初始在二维平面的原点 (0,0),他想前往坐标 (233,666).

在移动过程中,他只能采用以下两种移动方式,并且这两种移动方式可以交替、不限次数地使用:

  1. 水平向右移动,即沿着 x 轴正方向移动一定的距离。
  2. 沿着一个圆心在原点 (0,0)、以他当前位置到原点的距离为半径的圆的圆周移动,移动方向不限(即顺时针或逆时针移动不限)。

在这种条件下,他到达目的地最少移动多少单位距离?

只需输出答案四舍五入到整数的结果。

(2)解题思路

(3)代码

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char *argv[])
{
  // 先求出AC
  double Ac_dis = pow(pow(233, 2)+pow(666, 2), 0.5);
  double r = Ac_dis;
  // 接着求出BC
  double Bc_dis = acos(233 / Ac_dis) * r;
  // 四舍五入并输出结果
  int ans = (int)(Ac_dis + Bc_dis + 0.5);
  printf("%d", ans);
  return 0;
}

(4)难点:无

2、客流量上限

(1)题目描述

一家连锁旅馆在全国拥有 2025 个分店,分别编号为 1 至 2025。随着节日临近,总部决定为每家分店设定每日客流量的上限,分别记作 A1​,A2​,…,A2025​。这些上限并非随意分配,而是需要满足以下约束条件:

  • A1​,A2​,…,A2025​ 必须是 1 至 2025 的一个排列,即每个 Ai​ 均是 1 至 2025 之间的整数,且所有 Ai​ 互不相同。
  • 对于任意分店 i 和 j(1≤i,j≤2025,i 可等于 j),它们的客流量上限 Ai​ 和 Aj​ 的乘积不得超过 ij+2025。

这些约束旨在平衡各分店客流压力,确保服务质量和运营稳定性。

现在,请你计算这样的分配方案究竟有多少种。由于答案可能很大,你只需输出其对 109+7 取余后的结果即可。

(2)解题思路

(图 A)

(图 B)

列出题目限制的三个条件,从i = j时作为切入点,通过观察最终得到结论在1013<=i<=2025时,Ai = i。

然后讨论i ≠ j的情况,分为图B所示的三种情况,然后根据已经得到的“1013<=i<=2025时,Ai = i”结论,最后得1<= i <=1012时,Ai <= i + 1的结论。

(3)代码

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main(int argc, char *argv[])
{
  // 或者直接定义:long long mod = 1000000007LL;
  long long mod = 1;
  for (int i = 0; i < 9; i++) {
    mod *= 10;
  }
  mod += 7;

  long long ans = 1;
  for (int i = 0; i < 1012; i++) {
    ans *= 2;
    ans %= mod; // 必须实时取模
  }

  printf("%lld", ans);
  return 0;
}

(4)难点:纯数学问题,难。

3、可分解的正整数

(1)题目描述

定义一种特殊的整数序列:这种序列由连续递增的整数组成,并满足以下条件:

  1. 序列长度至少为 3。
  2. 序列中的数字是连续递增的整数(即相邻元素之差为 1),可以包括正整数、负整数或 0。

例如,[1,2,3]、[4,5,6,7] 和 [−1,0,1] 是符合条件的序列,而 [1,2](长度不足)和 [1,2,4](不连续)不符合要求。

现给定一组包含 N 个正整数的数据 A1​,A2​,…,AN​。如果某个 Ai​ 能够表示为符合上述条件的连续整数序列中所有元素的和,则称 Ai​ 是可分解的。

请你统计这组数据中可分解的正整数的数量。

(2)解题思路

(3)代码

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  /* 
  [−(x−1),−(x−2),...,−1,0,1,...,x−1,x]
  因此,在给定的正整数中,只有1不满足条件
  */
  int N;
  scanf("%d", &N);
  
  int ans = 0;
  for (int i = 0; i < N; i++) {
    int A;
    scanf("%d", &A);
    if (A != 1) ans++;
  }

  printf("%d", ans);
  return 0;
}

(4)难点:正负抵消的思维。

4、产值调整

(1)题目描述

偏远的小镇上,三兄弟共同经营着一家小型矿业公司“兄弟矿业”。公司旗下有三座矿山:金矿、银矿和铜矿,它们的初始产值分别用非负整数 AB 和 C 表示。这些矿山的产出是小镇经济的核心,支撑着三兄弟和许多矿工家庭的生计。

然而,各矿山的产值波动剧烈,有时金矿收益高而银矿、铜矿低迷,有时则相反。这种不稳定性让公司收入难以预测,也常引发兄弟间的争执。为了稳定经营,三兄弟设计了一个公平的产值调整策略,每年执行一次,每次调整时,将根据当前的产值 ABC,计算新产值:

  • 金矿新产值 A′=⌊2B+C​⌋;
  • 银矿新产值 B′=⌊2A+C​⌋;
  • 铜矿新产值 C′=⌊2A+B​⌋。

其中,⌊⌋ 表示向下取整。例如,⌊3.7⌋=3,⌊5.2⌋=5。

计算出 A′、B′、C′ 后,同时更新:A 变为 A′,B 变为 B′,C 变为 C′,作为下一年调整的基础。

三兄弟认为这个方法能平衡产值波动,于是计划连续执行 K 次调整。现在,请你帮他们计算,经过 K 次调整后,金矿、银矿和铜矿的产值分别是多少。

(2)解题思路

模拟+剪枝

(3)代码

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  int T;
  scanf("%d", &T);
  for (int i = 0; i < T; i++) {
    // 输入
    int A, B, C, K;
    scanf("%d", &A);
    scanf("%d", &B);
    scanf("%d", &C);
    scanf("%d", &K);
    // 计算
    int A_n, B_n, C_n;
    for (int j = 0; j < K; j++) {
      A_n = (B + C) / 2;
      B_n = (A + C) / 2;
      C_n = (A + B) / 2;
      // 剪枝,状态未变则退出
      if (A_n == A && B_n == B && C_n == C) break;
      A = A_n;
      B = B_n;
      C = C_n;
    }
    // 输出
    printf("%d %d %d\n", A, B, C);
  }
  return 0;
}

(4)难点:剪枝,和A B C变化的趋势。

5、画展布置

(1)题目描述

画展策展人小蓝和助理小桥为即将举办的画展准备了 N 幅画作,其艺术价值分别为 A1​,A2​,…,AN​。他们需要从这 N 幅画中挑选 M 幅,并按照一定顺序布置在展厅的 M 个位置上。如果随意挑选和排列,艺术价值的变化可能会过于突兀,导致观众的观展体验不够流畅。

为了优化布置,他们查阅了《画展布置指南》。指南指出,理想的画展应使观众在欣赏画作时,艺术价值的过渡尽量平缓。指南建议,选择并排列 M 幅画,应使艺术价值的变化程度通过一个数值 L 来衡量,且该值越小越好。数值 L 的定义为:L=i=1M1Bi+12Bi2其中 Bi​ 表示展厅第 i 个位置上画作的艺术价值。

现在,他们希望通过精心挑选和排列这 M 幅画作,使 L 达到最小值,以提升画展的整体协调性。请你帮他们计算出这个最小值是多少。

(2)解题思路

先假设M = 3,然后把L公式展开看下,哦,发现是求相邻元素平方差之和最小。

那么一定要排序且相邻,(其实排序就是为了相邻),但是是哪三个相邻的呢?那么就需要滑动窗口不断更新答案。

(3)代码

#include <stdio.h>
#include <stdlib.h>
#define MIN(a, b) ((a) > (b) ? b : a)
// cmp返回类型一定是int
int cmp(const void* a, const void* b) {
  long long n = (*(long long *)a);
  long long m = (*(long long *)b);
  if (n > m) return 1;
  if (n < m) return -1;
  return 0;
}
int main(int argc, char *argv[])
{ 
  // 1 输入
  int N, M;
  scanf("%d %d", &N, &M);
  long long arr[N];
  for (int i = 0; i < N; i++) {
    scanf("%lld", &arr[i]);
    arr[i] = arr[i]*arr[i];
  }
  // 2 排序
  qsort(arr, N, sizeof(long long), cmp);
  // 3 滑动窗口,更新答案,窗口大小为M
  long long ans = arr[N-1] - arr[0] + 1; // 已知该题一定有答案,先初始化一个不可能的答案
  for (int i = 0; i + M <= N; i++) {
    ans = MIN(ans, arr[i+M-1] - arr[i]);
  }
  // 4 输出答案
  printf("%lld", ans);
  return 0;
}

(4)难点:滑动窗口,不断更新答案。且”对于所有评测用例,2≤MN≤105,1≤Ai​≤105。”,所以要用long long。

6、水质检测

(1)题目描述

小明需要在一条 2×n 的河床上铺设水质检测器。在他铺设之前,河床上已经存在一些检测器。如果两个检测器上下或左右相邻,那么这两个检测器就是互相连通的。

连通具有传递性,即如果 A 和 B 连通,B 和 C 连通,那么 A 和 C 也连通。现在他需要在河床上增加铺设一些检测器,使得所有检测器都互相连通。他想知道最少需要增加铺设多少个检测器?

(2)解题思路

Dijkstra

(3)代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXN 1000005
#define INF 0x3f3f3f3f

// 存储点
typedef struct {
  int r, c;
} PII;

// 全局变量,防止栈溢出
char s[2][MAXN];
int dist[2][MAXN]; // dist[i][j]表示到达点(i,j)的最短路径
int st[2][MAXN]; // 标记数组,表示点(i,j)已找到最短路径
int n;

// 手写双端队列
PII deque[MAXN * 4];
int head = MAXN * 2, tail = MAXN * 2;


void push_front(int r, int c) {
  deque[--head] = (PII){r, c};
}

void push_back(int r, int c) {
    deque[tail++] = (PII){r, c};
}

PII pop_front() {
    return deque[head++];
}

int is_empty() {
    return head == tail;
}

// 0-1BFS:dijkstra
int main(int argc, char *argv[])
{
  // 数据读入
  scanf("%s %s", s[0], s[1]);
  n = strlen(s[0]);

  // 数组初始化
  memset(dist, 0x3f, sizeof(dist));
  memset(st, 0, sizeof(st));

  // 寻找第一个'#'作为源点
  int start_r = -1, start_c = -1;
  for (int j = 0; j < n; j++) {
    if (s[0][j] == '#') {
      start_r = 0;
      start_c = j;
      break;
    }
    if (s[1][j] == '#') {
      start_r = 1;
      start_c = j;
      break;
    }
  }

  // 剪枝
  if (start_r == -1) {
    printf("0\n");
    return 0;
  }

  // 0-1 BFS
  dist[start_r][start_c] = 0;
  push_back(start_r, start_c);

  int dx[] = {0, 0, 1, -1};
  int dy[] = {1, -1, 0, 0};
  int max_dis = 0;

  while (!is_empty()) {
    PII t = pop_front();
    int r = t.r, c = t.c;

    if (st[r][c]) continue;
    st[r][c] = 1; // 标记(r, c)点已获取到最短路径

    if (s[r][c] == '#') {
      max_dis = dist[r][c]; // 更新答案
    }

    for (int i = 0; i < 4; i++) {
      int nr = r + dx[i];
      int nc = c + dy[i];

      if (nr >= 0 && nr < 2 && nc >= 0 && nc < n) {
        int w = (s[nr][nc] == '#' ? 0 : 1);
        if (dist[nr][nc] > dist[r][c] + w) {
          dist[nr][nc] = dist[r][c] + w;
          if (w == 0) push_front(nr, nc);
          else push_back(nr, nc);
        }
      } 
    }
  }

  // 输出
  printf("%d\n", max_dis);
  return 0;
}

(4)难点:完全想不到,想到也不会实现。

7、生成车间

(1)题目描述

小明正在改造一个生产车间的生产流水线。这个车间共有 n 台设备,构成以 1 为根结点的一棵树,结点 i 有权值 wi​。

其中,叶结点的权值 wi​ 表示每单位时间产出 wi​ 单位材料并送往父结点;根结点的权值 wi​ 表示每单位时间内能打包 wi​ 单位成品; 其他结点的权值 wi​ 表示每单位时间最多能加工 wi​ 单位材料并送往父结点。

由于生产线中某些结点产能不足,导致无法正常运行,即某些结点每单位时间收到的材料超过其加工能力上限。小明计划删除一些结点使所有结点都能正常运行,想知道删除后根结点每单位时间最多能打包多少单位成品

(2)解题思路

DFS+DP,树形DP。

(3)代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXN 1005
#define MAXW 1005

int n;
int w[MAXN]; // w[i]表示节点i单位时间能承受单位材料的最大值
int adj[MAXN][MAXN]; // adj[i][j]表示节点j是否为节点i的子节点(输入的第一个是父节点,所以单向存储即可)
int dp[MAXN][MAXW]; // dp[i][j]表示节点i能否在单位时间向上拿j单位材料

int temp[MAXW];

void dfs(u, fa)
{
  int is_leaf = 1;
  // 初始化
  dp[u][0] = 1;
  // 核心
  for (int v = 1; v <= n; v++) {
    if (adj[u][v]) {
      is_leaf = 0;
      dfs(v, u);
      memset(temp, 0, sizeof(temp));

      for (int i = 0; i <= w[u]; i++) {
        if (dp[u][i] == 1) {
          for (int j = 0; j <= w[v]; j++) {
            if (dp[v][j] == 1 && i + j <= w[u]) {
              temp[i + j] = 1;
            }
          }
        }
      }

      // 存储结果
      for (int i = 0; i <= w[u]; i++) {
        dp[u][i] = temp[i]; // 不断完善dp[u]中
      }
    } 
  }
  if (is_leaf) {
    dp[u][0] = 1;
    dp[u][w[u]] = 1;
  }
}

int main(int argc, char *argv[])
{
  // 1 输入
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    scanf("%d", &w[i]);
  }
  for (int i = 0; i < n - 1; i++) {
    int u, v;
    scanf("%d %d", &u, &v);
    adj[u][v] = 1;
  }
  // 2 DFS构造dp数组
  dfs(1, 0);
  // 3 输出答案
  int ans;
  for (int x = w[1]; x >= 0; x--) {
    if (dp[1][x] == 1) {
      ans = x;
      break;
    }
  }
  printf("%d\n", ans);
  return 0;
}

(4)难点:完全想不到,想到也不会实现。

8、装修报价

(1)题目描述

老王计划装修房子,于是联系了一家装修公司。该公司有一套自动报价系统,只需用户提供 N 项装修相关费用 A1​,A2​,…,AN​,系统便会根据这些费用生成最终的报价。

然而,当老王提交数据后,他发现这套系统的运作方式并不透明:系统只会给出一个最终报价,而不会公开任何运算过程或中间步骤。

公司对此解释称,这套系统会依据某种内部算法,在每对相邻数字之间插入 +(加法)、−(减法)或 ⊕(异或)运算符,并按照特定优先级规则计算总和:异或运算优先级最高,其次是加减。但由于保密性,具体的运算符组合以及中间过程都不会对外公开。

为了验证系统报价是否合理,老王决定模拟其运作方式,尝试每种可能的运算符组合,计算出所有可能出现的总和。如果最终报价明显超出这个范围,他就有理由怀疑系统存在异常或误差。只是老王年事已高,手动计算颇为吃力,便向你求助。

现在,请你帮老王算出所有可能的总和。由于该总和可能很大,你只需提供其对 109+7 取余后的结果即可。

(2)解题思路

由于异或优先级最高,整个表达式被加减号切分成多个“异或块”,而在求所有组合总和时,加减号后的所有后续变化都会因正负对称而相互抵消为 0,因此只需遍历每一个可能成为“第一个加减号前缀”的异或块 s,计算它在所有可能组合中的贡献,最后累加上唯一的“全异或”情况即可。

(3)代码

#include <stdio.h>

#define MOD 1000000007

int main() {
    // 1 输入
    int n;
    scanf("%d", &n);

    int a[n + 1];
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }

    // 预处理数组
    long long mi[n + 1];
    mi[0] = 1;
    for (int i = 1; i <= n; i++) {
        mi[i] = (mi[i - 1] * 3) % MOD;
    }

    // 2 数学逻辑
    long long ret = 0;
    long long s = 0;

    for (int i = 1; i < n; i++) {
        s ^= a[i];
        
        long long contribution = (s * 2) % MOD;
        contribution = (contribution * mi[n - i - 1]) % MOD;
        
        ret = (ret + contribution) % MOD;
    }

    s ^= a[n];
    ret = (ret + s) % MOD;

    printf("%lld\n", ret % MOD);

    return 0;
}

(4)难点:数学。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注