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


往期蓝桥杯真题解析

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

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

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

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

【蓝桥杯】算法训练 无聊的逗

【蓝桥杯】算法提高 打包

【蓝桥杯】算法提高 学生节


引言

Hello大家好啊,我是秋刀鱼,今天因为时间比较宽裕所以又给大家出题解啦,希望能够得到大家的支持!

今天的题目不算太难(可能是昨天太难了),但是题目中涉及到的知识点还是很重要的,包括全排列、并查集、BFS搜索、贪心与二分算法,而且每一道题都是细节满满,希望你能耐心看完本篇题解,相信你一定能有所收获。


带分数

题目传送门

问题描述

100 可以表示为带分数的形式:100 = 3 + 69258 / 714。

还可以表示为:100 = 82 + 3546 / 197。

注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。

类似这样的带分数,100 有 11 种表示法。

输入格式

从标准输入读入一个正整数N (N<1000*1000)

输出格式

程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。

注意:不要求输出每个表示,只统计有多少表示法!

样例输入1

100

样例输出1

11

样例输入2

105

样例输出2

6

解题思路

这道题的核心思路是全排列,既然要是 1~9 的数字不能重复出现,那么将 1~9 的数字全排列并存储与数组中。,题目要求满足的式子: a + b / c = t a r g e t a+b/c = target a+b/c=target,那么定义两个指针idx1idx2将全排列中的数据进行分割,分割3个数字 a,b,c:

[0,idx1) a
[idx1,idx2] b
(idx2,n) c

根据索引范围可以得到这三个数的具体值,将a+b/c的值与target进行一个比较,就可以获得答案。

AC代码

#include <iostream>
#include <cstring>
using namespace std;
int value[10];
int ans = 0;
int target;
// 获取[l,r]索引位置上的数
int getValue(int l, int r) {
    
    
    int ret = 0;
    while (l <= r) {
    
    
        ret *= 10;
        ret += value[l];
        ++l;
    }
    return ret;
}
void judge() {
    
    
    // [0,idx1) 第一个数
    // [idx1,idx2] 第二个数
    // (idx2,n) 第三个数
    int idx1, idx2;
    for (idx1 = 0; idx1 <= 10; ++idx1) {
    
    
        for (idx2 = idx1; idx2 < 10; ++idx2) {
    
    
            int value1 = getValue(0, idx1 - 1);
            int value2 = getValue(idx1, idx2);
            int value3 = getValue(idx2 + 1, 8);
            if (value3 == 0||value2%value3!=0) {
    
    
                continue;
            }
          
            if (value1+value2/value3==target){
    
    
                ++ans;
            }
        }
    }
}
// 全排列递归
void dfs(int low,int high) {
    
    
    if (low==high) {
    
    
        judge();
        return;
    }
    for (int i = low; i < high; ++i) {
    
    
        swap(value[i], value[low]);
        dfs(low + 1, high);
        swap(value[i], value[low]);
    }
}
int main()
{
    
    
    cin >> target;
    memset(value, 0, sizeof(value));
    for (int i = 0; i < 9; ++i) {
    
    
        value[i] = i + 1;
    }
    dfs(0, 9);
    cout << ans;
    return 0;
}

走迷宫

题目传送门

题目描述

给定一个 N×M* 的网格迷宫 GG 的每个格子要么是道路,要么是障碍物(道路用 1 表示,障碍物用 0 表示)。

已知迷宫的入口位置为 (x1,y1),出口位置为 (x2,y2)。问从入口走到出口,最少要走多少个格子。

输入描述

输入第 11 行包含两个正整数 N*,M,分别表示迷宫的大小。

接下来输入一个 N×M 的矩阵。若 i*,*j=1 表示其为道路,否则表示其为障碍物。

最后一行输入四个整数 x_1,y_1,x_2,y_2x1,y1,x2,y2,表示入口的位置和出口的位置。

输出描述

输出仅一行,包含一个整数表示答案。

若无法从入口到出口,则输出 -1−1。

输入输出样例

示例 1

输入

5 5 
1 0 1 1 0
1 1 0 1 1 
0 1 0 1 1
1 1 1 1 1
1 0 0 0 1
1 1 5 5 

输出

8

解题思路

基础的一道BFS搜索问题,搜索最短路径使用宽度优先遍历准没有错。

#include <iostream>
#include <cstring>
#include <queue>
#define pii pair<int,int>
#define M 110
using namespace std;
int points[M][M];
int n, m;
int ans = -1;
int directs[4][2]{
    
     {
    
    1,0},{
    
    -1,0},{
    
    0,1},{
    
    0,-1} };
void bfs(int x, int y, int endX, int endY) {
    
    
    queue<pii>qu;
    qu.push({
    
     x,y });
    // 保存步数
    int t = 0;
    while (qu.size()) {
    
    
        int len = qu.size();
        // 开启一轮遍历,一轮遍历中遍历到的点其走过的步数都是相同的,为t
        for (int i = 0; i < len; ++i) {
    
    
            pii tmp = qu.front();
            qu.pop();
		   // 遍历4个方向
            for (int k = 0; k < 4; ++k) {
    
    
                int nx = tmp.first + directs[k][0];
                int ny = tmp.second + directs[k][1];
                if (nx < 0 || ny < 0 || nx >= n || ny >= m || points[nx][ny] == 0) {
    
    
                    continue;
                }
                if (nx == endX && ny == endY) {
    
    
                    ans = t + 1;
                    return;
                }
                qu.push({
    
     nx,ny });
                points[nx][ny] = 0;
            }
        }
        ++t;
    }
}
int main()
{
    
    
    cin.tie();
    cin >> n >> m;
    for (int i = 0; i < n; ++i) {
    
    
        for (int j = 0; j < m; ++j) {
    
    
            cin >> points[i][j];
        }
    }
    int x, y, endX, endY;
    cin >> x >> y >> endX >> endY;
    // 转换为数组索引
    --x; --y; --endX; --endY;
    bfs(x, y, endX, endY);
    cout << ans;
    return 0;
}

蓝桥幼儿园

题目传送门

题目描述
蓝桥幼儿园的学生是如此的天真无邪,以至于对他们来说,朋友的朋友就是自己的朋友。

小明是蓝桥幼儿园的老师,这天他决定为学生们举办一个交友活动,活动规则如下:

小明会用红绳连接两名学生,被连中的两个学生将成为朋友。

小明想让所有学生都互相成为朋友,但是蓝桥幼儿园的学生实在太多了,他无法用肉眼判断某两个学生是否为朋友。于是他起来了作为编程大师的你,请你帮忙写程序判断某两个学生是否为朋友(默认自己和自己也是朋友)

输入描述

输出描述
对于每个op=2的输入,如果z和y是朋友,则输出一行YES,否则输出一行NO。

输入输出样例
输入:

5 5
2 1 2
1 1 3
2 1 3
1 2 3
2 1 2
1
2
3
4
5
6
输出:

NO
YES
YES

解题思路

这道题算是并查集的模板题,如果没有学过的同学一定要预先学习一下并查集算法再作答。

本题如果直接使用并查集木板可能会超时,需要将关系图压缩优化一下。如果将关系比喻成一个多叉树,那么多叉树的高度决定了查询关系时的性能。

如果有如下关系:

image-20220321185322786

fa[1]=fa[2]=fa[3]=fa[4]=4,那么当查询 1 结点的父节点时,需要遍历的次数为3,并且随着层数的增高遍历的次数也会增多,为了优化查找速度,可行的方法是在查询到 1 结点正在的父节点时 4 后,此时直接修改 1 号结点的关系将 1 号结点与 4 号结点绑定:

image-20220321185642406

这样一来,下一次查询 1 号结点的父节点时,需要查询的次数减少为1次,极大地提高了查询性能。希望同学们能记住这种优化方法,只需要简单的一行代码能让性能提升不少!

AC代码

#include <iostream>
#define M 200010
using namespace std;
// 定义并查集类
class UnionFind {
    
    
private:
    // 保存关系
    int fa[M];
public:
    UnionFind(int n) {
    
    
        for (int i = 1; i <= n; ++i) {
    
    
            fa[i] = i;
        }
    }
    int find(int idx) {
    
    
        if (fa[idx] == idx) {
    
    
            return idx;
        }
        // 改变关系,提高查询速度
        fa[idx]=find(fa[idx]);
        return fa[idx];
    }
    void merge(int n, int m) {
    
    
        int fa1 = find(n);
        int fa2 = find(m);
        if (fa1 == fa2) {
    
    
            return;
        }
        fa[fa1] = fa2;
    }
};
int main()
{
    
    
    int n, m;
    cin >> n >> m;
    UnionFind fa(n);
    for (int i = 1; i <= m; ++i) {
    
    
        int op, s1, s2;
        cin >> op >> s1 >> s2;
        if (op == 1) {
    
    
            fa.merge(s1, s2);
        }
        else {
    
    
            int fa1 = fa.find(s1);
            int fa2 = fa.find(s2);
            cout << (fa1 == fa2 ? "YES" : "NO") << endl;
        }
    }
    return 0;
}

跳石头

题目传送门

题目描述
一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
输入描述:

输入文件第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。
接下来 N 行,每行一个整数,第 i 行的整数 Di(0 < Di < L)表示第 i块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出描述:

输出文件只包含一个整数,即最短跳跃距离的最大值。

示例1
输入
复制

25 5 2
2
11
14
17
21

输出:

4

题目解析

总体解决思路是贪心+二分,不过相较于其他类似题目本题很具有代表性,需要考虑的细节也更多。

河道中在终点与起点之间分布着 N 块石头,终点与起点的距离为 L ,选手只能顺序地从起点跳往终点。委员会能在起点终点之间拿走 M 块石头,使得最短跳跃距离最大。

首先起点与终点的距离为L,也就是说如果拿走起点终点间的 N 块石头即 M = N,此时最短跳跃距离为 L ;那么我们可以大致推断,最短跳跃距离的最大值区间范围为: [ 0 , L ] [0,L] [0,L]

对于 [ 0 , L ] [0,L] [0,L] 的区间取值,定义中间值为 m m m 为最短跳跃距离。现在从第一个石头出发,如果跳跃的距离小于 m m m ,我们就移除该石头。比较移除石头的次数 t t t 与最大移除次数 M M M ,如果 t < = M t<=M t<=M 表明该最大值下能够跳跃,缩小区间最终得到答案。

细节一:处理无限循环情况

假设区间缩小为 [ 2 , 3 ] [2,3] [2,3] ,得到中间值 m = 2 m = 2 m=2 如果符合则 l = m,也就是说区间仍然是 [ 2 , 3 ] [2,3] [2,3] ,继续判断于是进入了死循环。为了避免死循环的出现,我们可以修改 m m m 中间值的获取方式,更改为:m=(l+r+1)/2

AC代码

#include <iostream>
using namespace std;
#define MAX 50010

int stones[MAX];

int L, N, M;
bool check(int gap) {
    
    
    int t = 0;
    int pre = 0;
    for (int i = 1; i <= N; ++i) {
    
    
        if (stones[i] - pre < gap) {
    
    
            ++t;
        }
        else {
    
    
            pre = stones[i];
        }
    }
    return t <= M;
}
int main()
{
    
    
    cin >> L >> N >> M;
    for (int i = 1; i <= N; ++i) {
    
    
        cin >> stones[i];
    }
    int l, r;
    l = 0; r = L;
    while (l < r) {
    
    
        int m = (l + r + 1) / 2;
        if (check(m)) {
    
    
            l = m;
        }
        else {
    
    
            r = m - 1;
        }
    }
    cout << l;
    return 0;
}

细节二:处理终点问题

如上的代码确实能够AC,蓝桥杯官网中也能够通过,但是这就是这道题的最终解了吗?可能并不是。我之前有说过这道题细节满满,还有如下需要注意。

题目中有说到:***组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)***

注意到:起点与终点都是岩石,且就算是到达了中间的第 N N N 块石头,距离终点的第 N + 1 N+1 N+1 块石头也是有一段距离的,这一段距离当然也需要一次跳跃,这一次跳跃同样会影响到最短跳跃距离。因为最后的一块石头不能被拿走,因此最后一次的跳跃距离不管远近都不会影响组委会移动石头的次数,但是却能够直接影响答案。而上述的代码只考虑了 [ 1 , N ] [1,N] [1,N] 块岩石的跳跃情况,没有判断最后一次跳跃。

正确的判断是,在退出check方法之前,判断 p r e pre pre 与 终点的距离,如果这个距离本身就小于传入的最短跳跃距离,那么直接返回false。

AC代码2

#include <iostream>
using namespace std;
#define MAX 50010

int stones[MAX];

int L, N, M;
bool check(int gap) {
    
    
    int t = 0;
    int pre = 0;
    for (int i = 1; i <= N; ++i) {
    
    
        if (stones[i] - pre < gap) {
    
    
            ++t;
        }
        else {
    
    
            pre = stones[i];
        }
    }
    // 获取最后一步的距离
    int red = L - pre;
    // 如果最后一步距离比 gap 更小直接返回
    if(red < gap){
    
    
      return false;
    }
    return t <= M;
}
int main()
{
    
    
    cin >> L >> N >> M;
    for (int i = 1; i <= N; ++i) {
    
    
        cin >> stones[i];
    }
    int l, r;
    l = 0; r = L;
    while (l < r) {
    
    
        int m = (l + r + 1) / 2;
        if (check(m)) {
    
    
            l = m;
        }
        else {
    
    
            r = m - 1;
        }
    }
    cout << l;
    return 0;
}

写在最后

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

如果觉得博主写的不错的话,可以点赞支持一下!ღ( ´・ᴗ・` )比心

狗勾揣手手表情包

猜你喜欢

转载自blog.csdn.net/qq_51439643/article/details/123643503