【蓝桥杯冲刺 day17】今日四道真题全解析

本文详细解析了蓝桥杯编程竞赛中的四道题目,包括计算字符串中字符间距离的总和、多源BFS扩散问题、错误票据的模拟处理及寻找特定条件的三个整数倍数问题。通过AC代码展示了解题思路和解决方案,并提供了相关题目的拓展思考。

写在前面

Hello大家好我是秋刀鱼,今天给大家带来每日蓝桥杯真题题解。

今天的题目比较简单,但就算遇到简单题也不能松懈,拿下全分才是关键!


往期蓝桥杯真题解析

【十二届蓝桥杯国赛真题】123 — 时间复杂度O(1)的纯数学解法

【蓝桥杯真题训练 day14】今日四道真题全解析

【蓝桥杯冲刺 day12】题目全解析

【蓝桥杯冲刺 day10】题目全解析 — 难题突破

【蓝桥杯冲刺 day8】题目全解析 —附上LeetCode 每日一题

【蓝桥杯冲刺 day7】 题目全解析 — 附上LeetCode周赛 银联-03. 理财产品

【蓝桥杯冲刺 day4】题目全解析 — 每日刷题解析


距离和

题目传送门🚀

image-20220324171144031

题目解析

很简单的一道题,题目中说明两个字母间的距离是字母表中的位置,那么只需要让任意两个字符相减就能获得字母间距,枚举每一对字母求得其间距之和,就能够获得答案。

AC代码

#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;
int main()
{
  int ans=0;
  string str = "LANQIAO";
  for(int i=0;i<str.size();++i){
    for(int j=i+1;j<str.size();++j){
      ans+=abs(str[j]-str[i]);
    }
  }
  cout<<ans;
  return 0;
}

扩散

题目传送门🚀
在这里插入图片描述

题目解析

一道多源的BFS搜索题目,可以从题目中给定的4个点出发,使用BFS搜索2020次,将搜索到的点做上标记。最终遍历所有可能搜索到的点,求得标记点的数量和即是答案。

虽然说BFS能够解决这道题,但该解法从时间速度与内存消耗角度来看并不是最优解。

这种从某个点开始,向任意方向的扩散问题,均能够转换为求解 曼哈顿距离 解决,这题也不例外。

曼哈顿距离:定义 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2),这两点的曼哈顿距离就是 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ |x_1-x_2|+|y_1-y_2| x1x2+y1y2

既然最大能够扩散次数为2020,我们只需要搜索所有可能到的的点中,距离任意一个起始点曼哈顿距离 <= 2020的点,就是能够被染成黑色的点。

因此我们就能将这道题转换为求解曼哈顿距离。

那么可能到达点范围使用maxX,maxY,minX,minY圈出,遍历该范围中所有的点即可获得答案。

AC代码

#include <iostream>
#include <math.h>
#define S 2020
using namespace std;
// 获得两个点的曼哈顿距离
int ManHaTonDistance(int x1,int y1,int x2,int y2){
  return abs(x1-x2)+abs(y1-y2);
}
int main()
{
  int pos[4][2]={{0,0},{2020,11},{11,14},{2000,2000}};
  int minX,maxX,minY,maxY;
    
  minX=0;
  maxX=2020;
  minY=0;
  maxY=2000;

    // 扩散范围
  minX-=S;
  minY-=S;
  maxY+=S;
  maxX+=S;

  int ans=0;
  for(int x=minX;x<=maxX;++x){
    for(int y=minY;y<=maxY;++y){
      for(int i=0;i<4;++i){
        if(ManHaTonDistance(x,y,pos[i][0],pos[i][1])<=S){
          ++ans;
          break;
        }
      }
    }
  }
  cout<<ans;
  return 0;
}

错误票据

题目传送门🚀

image-20220324172907069

题目解析

可以把这道题当做模拟题来做。

  • 定义 set 存放出现过的 ID 号

  • 定义 map 存放断号次数

1、判断是否重复

添加一个数值时,set 中已经存在该数值,即能判断该值重复。

2、判断断号

添加一个数值 v a l val val 时,如果 v a l + 1 val+1 val+1 没有出现过,那么我就将 v a l + 1 val+1 val+1map 中对应的断号次数加一,对 v a l − 1 val-1 val1 也执行这样的操作。添加一个数值 v a l val val 后,因为该数值 v a l val val 已经出现,所以删去 map v a l val val 数值的键值对。

最终遍历结束后,map 中存放断号次数为2的值,就是我们要找的断号。

AC代码

// 1:无需package
// 2: 类名必须Main, 不可修改

import java.io.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

// 快读类
class cin{
    static PrintWriter writer = new PrintWriter(new OutputStreamWriter(System.out));
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    static StreamTokenizer in = new StreamTokenizer(reader);

    public static int nextInt() throws IOException {
        in.nextToken();
        return (int) in.nval;
    }
    public static double nextDouble() throws IOException {
        in.nextToken();
        return in.nval;
    }
    public static long nextLong() throws IOException{
        in.nextToken();
        return (long) in.nval;
    }
    public static String nextLine() throws IOException{
        return reader.readLine();
    }
}

public class Main {
    static Set<Integer> numberSet = new HashSet<>();
    static Map<Integer, Integer> used = new HashMap<>();
    public static void add(int val){
        if (numberSet.contains(val)) {
            return;
        }
        used.put(val, used.getOrDefault(val, 0) + 1);
    }
    public static void main(String[] args) throws IOException {
        int n = cin.nextInt();
        int gapNum = -1;
        int doubleNUm = -1;
        for (int i = 0; i < n; ++i) {
            String[] str = cin.nextLine().split(" ");
            for (String s : str) {
                int val = Integer.parseInt(s);
                if (numberSet.contains(val)) {
                    doubleNUm = val;
                }
                numberSet.add(val);
                used.remove(val);
                int pre = val - 1;
                int next = val + 1;
                add(pre);
                add(next);
            }
        }
        for (Map.Entry<Integer, Integer> entry : used.entrySet()) {
            if (entry.getValue() >= 2) {
                gapNum = entry.getKey();
                break;
            }
        }
        System.out.println(gapNum + " " + doubleNUm);
    }
}

倍数问题

image-20220324174510868

解题思路

解题的核心是使用贪心算法。

只需要找到三个数,三个数定义为 a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3 ,那么一定有 ( a 1 + a 2 + a 3 ) % k = = 0 (a_1+a_2+a_3) \%k ==0 (a1+a2+a3)%k==0。根据取余的性质有: ( ( a 1 % k ) + ( a 2 % k ) + ( a 3 % k ) ) % k = = 0 ((a_1\%k)+(a_2\%k)+(a_3\%k))\%k==0 ((a1%k)+(a2%k)+(a3%k))%k==0 。将待取值 a i a_i ai 按照 a i % k a_i\%k ai%k 的值进行分组,每一组对结果值的影响是相同的。

举个栗子:

现在如果有:0,1 2 3 4 5 6 7 此时 K = 3 K =3 K=3

那么分组情况为: 0,3,6 分别第0组, 1,4,7 分为第 1组 ,2,5 分为第2 组,组号为取余 K 后的值。

那么为了凑出 K ,现在我需要从这些组中取出数,怎么取呢?还记得 ( ( a 1 % k ) + ( a 2 % k ) + ( a 3 % k ) ) % k = = 0 ((a_1\%k)+(a_2\%k)+(a_3\%k))\%k==0 ((a1%k)+(a2%k)+(a3%k))%k==0 吗?

引入组的概念: ( a 1 组 号 + a 2 组 号 + a 3 组 号 ) % k = = 0 (a_1组号 + a_2 组号 + a_3组号)\%k==0 (a1+a2+a3)%k==0 ,即三个数组号之和取余 K 为 0 ,满足题意。

由此可见定义两个指针 i , j i,j i,j 枚举组的情况,再根据前面的公式,获得第三个组的组号 z z z ,获得的 i , j , z i,j,z i,j,z 分别是 a 1 组 号 , a 2 组 号 , a 3 组 号 a_1组号,a_2组号,a_3组号 a1a2a3

有了组号后,我们能从该组中取出值,假如 i = 0 , j = 0 , z = 0 i=0,j=0,z=0 i=0,j=0,z=0 ,也就是说要取出3个 0 组的值,取出的分别是0,3,6,符合题意。假如 i = 1 , j = 2 , z = 0 i=1,j=2,z=0 i=1,j=2,z=0 ,取出 0 组一个值,取出 1 组一个值,取出 2 组 个值,取出的分别是0,1,2,符合题意。

但是不要忘了,如果遍历到的是 i = 2 , j = 2 i=2,j=2 i=2,j=2,计算得 k = 2 k=2 k=2 ,意味着需要取出 2 组3个值,但此时 2 组一共只有两个,无法取出三个,所以不符合题意。

总结一下就是: i , j i,j i,j 都从0开始遍历所有组,根据 ( i + j + z ) % k = = 0 (i + j + z)\%k==0 (i+j+z)%k==0 得到第三组的组号,尝试去组 i,j,z指向的组中取值,如果组中够取那么符合题意,如果组中值个数不足,不符合题意。

但是现在题目中要求的是,取出的值最大,如何处理呢?

题目给出最多取 3 个值,也就是说组中只需要保留 3 个值数据,哪三个值呢?最大的三个值,且每次取值按照值从大到小顺序取出,这样就能保证符合题意的值最大。

AC代码

#include <iostream>
#include <string.h>
#include <climits>
#define M 1001
#define ll long long
using namespace std;
ll arr[M][3];
int main()
{
    int n,k;
    cin >> n>>k;
    for (int i = 0; i < k; ++i) {
        for (int j = 0; j < 3; ++j) {
            arr[i][j] = INT_MIN;
        }
    }
    while (n--) {
        ll val,mod;
        cin >> val;
        mod = val % k;
        
        // 按照从大到小顺序排
        if (arr[mod][0] < val) {
            arr[mod][2] = arr[mod][1];
            arr[mod][1] = arr[mod][0];
            arr[mod][0] = val;
        }
        else if (arr[mod][1] < val) {
            arr[mod][2] = arr[mod][1];
            arr[mod][1] = val;
        }
        else if (arr[mod][0] < val) {
            arr[mod][0] = val;
        }
    }
    ll ans = INT_MIN;
    for (int i = 0; i < k; ++i) {
        for (int j = 0; j < k; ++j) {
            int z = k-(i + j + k) % k;
            ll val1, val2, val3;
            val1 = arr[i][0];
            // 取值逻辑
            
            if (i == j && j == z) {
                val2 = arr[i][1];
                val3 = arr[i][2];
            }
            else if (i == j) {
                val2 = arr[i][1];
                val3 = arr[z][0];
            }
            else if (j == z) {
                val2 = arr[j][0];
                val3 = arr[j][1];
            }
            else if (i == z) {
                val2 = arr[j][0];
                val3 = arr[i][1];
            }
            else {
                val2 = arr[j][0];
                val3 = arr[z][0];
            }
            if (val1 == INT_MIN || val2 == INT_MIN || val3 == INT_MIN) {
                continue;
            }
            ans = max(ans, val1 + val2 + val3);
        }
    }
    cout << ans;
    return 0;
}

写在最后

代码、论述中有任何问题,欢迎大家指出,同时如果有任何疑问,也能够在评论区中留言,大家共同讨论共同进步!

如果觉得博主写的不错的话,可以点赞支持一下

img

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秋刀鱼与猫_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值