蓝桥杯C++搜索算法&二分法题目精选

一、深度遍历算法

1. 四十二点

题目描述
请你设计一个程序对该问题进行解答。
众所周知在扑克牌中,有一个老掉牙的游戏叫做 24 点,选取 4 张牌进行加减乘除,看是否能得出 24 这个答案。现在小蓝同学发明了一个新游戏,他从扑克牌中依次抽出6张牌,注意不是一次抽出,进行计算,看是否能够组成 42 点,满足输出 YES ,反之输出 NO 。最先抽出来的牌作为第一个操作数,抽出牌做第二个操作数,运算结果再当作第一个操作数,继续进行操作。
注:除不尽的情况保留整数,而且扑克牌的四张 10 都丢了,不会出现 10,1和A都可能出现并表示1。

输入描述
输出仅一行包含 6 个字符。
保证字符 ∈ {1, 2, 3, 4, 5, 6, 7, 8, 9, J, Q, K, A}。

输出描述
若给出到字符能够组成 42 点,满足输出 YES ,反之输出 NO 。

输入输出样例

示例

输入

K A Q 6 2 3 

输出

YES 

题解

#include<iostream>
using namespace std;
int a[10];

// 定义深度优先搜索(DFS)函数,参数i表示当前处理的元素索引,pre表示前一个元素与当前计算结果
bool dfs(int i, int pre) {
    // 当索引i等于5时,说明已经处理完所有6个元素
    if (i == 5) {
        // 检查pre与最后一个元素(a[5])进行加、减、乘、除运算后是否得到42
        if (pre + a[5] == 42 || abs(pre - a[5]) == 42 || pre * a[5] == 42 || pre / a[5] == 42)
            return true;
        else
            return false;
    }

    // 递归调用dfs函数,分别尝试四种运算方式:加、减(取绝对值)、乘、除
    bool x = dfs(i + 1, a[i] + pre);
    bool b = dfs(i + 1, abs(pre - a[i]));
    bool c = dfs(i + 1, a[i] * pre);
    bool d = dfs(i + 1, a[i] / pre);

    // 如果四种运算方式中任意一种能导致dfs返回true,则返回true
    if (x || b || c || d)
        return true;
    else
        return false;
}

int main() {
    // 读入6个字符(代表扑克牌),并将其转换为对应的数值存入数组a
    for (int i = 0; i < 6; i++) {
        char c;
        cin >> c;
        if (c == 'A')  a[i] = 1; 
        else if (c == 'J')  a[i] = 10; 
        else if (c == 'Q')  a[i] = 11; 
        else if (c == 'K')  a[i] = 12; 
        else  a[i] = c - '0'; 
    }

    // 调用dfs函数,以a[0]作为初始计算结果,检查是否能通过一系列运算得到42
    if (dfs(1, a[0]))
        cout << "YES" << endl;
    else
        cout << "NO" << endl;
    return 0;
}

2. 走迷宫(DFS)

题目描述
给定一个 N × M 的网格迷宫 G。G 的每个格子要么是道路,要么是障碍物(道路用 1 表示,障碍物用 0 表示)。已知迷宫的入口位置为 (x1, y1),出口位置为 (x2, y2)。问从入口走到出口,最少要走多少个格子。

输入描述
输入第 1 行包含两个正整数 N, M,分别表示迷宫的大小。接下来输入一个 N × M 的矩阵。若 G[i,j] = 1 表示其为道路,否则表示其为障碍物。最后一行输入四个整数 x1, y1, x2, y2,表示入口的位置和出口的位置。1 ≤ N, M ≤ 10^2,0 ≤ G[i,j] ≤ 1,1 ≤ x1, x2 ≤ N,1 ≤ y1, y2 ≤ M。

输出描述
输出仅一行,包含一个整数表示答案。
若无法从入口到出口,则输出 -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

题解

#include <iostream>
using namespace std;

int vis[150][150]; // 用于存储是否访问过,并且存储长度
int G[150][150]; // 用于存储题目给出的地图
int n, m;
int ans = 1e4;
int dx[4] = { 0, 0, -1, 1 };
int dy[4] = { 1, -1, 0, 0 };

// 定义结构体pii表示坐标点
struct pii {
    int x;
    int y;
};

pii Start, End;

// pd函数判断给定的坐标(x, y)是否在地图范围内,未被访问过且为可通行路径
bool pd(int x, int y) {
    return x >= 1 && x <= n && y >= 1 && y <= m && vis[x][y] == 0 && G[x][y] == 1;
}
//5 5
//1 0 1 1 0
//1 1 0 1 1
//0 1 0 1 1
//1 1 1 1 1
//1 1 1 1 1
//1 1 5 5
// dfs函数实现深度优先搜索寻找最短路径
int flag = 0;
void dfs(int x, int y, int steps) {
    if (x == End.x && y == End.y) {
        ans = min(ans, steps);
        flag = 1;
        return;
    }

    for (int i = 0; i < 4; i++) {
        int nextx = x + dx[i];
        int nexty = y + dy[i];

        if (pd(nextx, nexty)) {
            vis[nextx][nexty] = 1;
            dfs(nextx, nexty, steps + 1);
            vis[nextx][nexty] = 0; // 回溯
        }
    }
}



int main() {
    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> G[i][j];
        }
    }

    cin >> Start.x >> Start.y >> End.x >> End.y;
    vis[Start.x][Start.y] = 1;
    dfs(Start.x, Start.y, 0);
    if (flag)cout << ans;
    else cout << '-1';
    return 0;
}

二、宽度遍历算法

1. 走迷宫(BFS)

#include <iostream>
#include <queue>
using namespace std;

// 定义一个全局变量vis,用于存储地图中每个点是否被访问过以及从起点到该点的步数
int vis[150][150];

// 定义一个二维字符数组G,存储题目给出的地图信息,'1'代表可通过的道路
char G[150][150];

// 定义地图的行数(n)、列数(m)以及当前找到的最短路径长度(ans)
int n, m, ans = 0;

// 定义四个方向的移动偏移量:上、下、左、右
int dx[4] = { 0, 0, -1, 1 };
int dy[4] = { 1, -1, 0, 0 };
// 两两组合形成上下左右四个方向
//      1------------------> x
//      |
//      |
//      |
//      |
//      |
//      |
//      |
//      ↓
//      y

// dx[0]=0 dy[0]=1 那么代表向下的方向

// dx[1]=1 dy[1]=0 那么代表向右的方向

// dx[2]=-1 dy[2]=0 那么代表向左的方向

// dx[3]=0 dy[3]=-1 那么代表向上的方向

// 结构体node用于表示地图上的一个点,包含横纵坐标x和y
struct node
{
    int x;
    int y;
};

// 起点和终点
node Start, End;

// pd函数判断给定的坐标(x, y)是否在地图范围内且未被访问过,并且当前位置是可通行的路径
bool pd(int x, int y)
{
    if (x < 1 || x > n || y < 1 || y > m || vis[x][y] != 0 || G[x][y] != '1')
        return 0;
    else
        return 1;
}

// check函数检查当前节点是否为终点,若是,则更新最短路径长度
bool check(int x, int y)
{
    if (x == End.x && y == End.y)
    {
        ans = vis[x][y];
        return 1;
    }
    else
        return 0;
}

// bfs函数实现宽度优先搜索算法寻找最短路径
void bfs()
{
    queue<node> q; // 使用队列来实现BFS

    node now, next; // 当前节点now和下一个可能的节点next

    q.push(Start); // 将起点压入队列
    vis[Start.x][Start.y] = 1; // 初始化起点已访问,距离为1

    while (!q.empty())
    {
        now = q.front(); // 获取队列头节点
        q.pop(); // 弹出队列头节点

        if (check(now.x, now.y)) // 检查是否到达终点
            return;

        for (int i = 0; i < 4; i++) // 遍历四个方向
        {
            int nextx = now.x + dx[i]; // 计算下一个可能位置的横坐标
            int nexty = now.y + dy[i]; // 计算下一个可能位置的纵坐标

            if (pd(nextx, nexty)) // 若新位置合法且未访问过
            {
                next.x = nextx;
                next.y = nexty;

                q.push(next); // 将新位置压入队列
                vis[nextx][nexty] = vis[now.x][now.y] + 1; // 更新步数,即距离+1
            }
        }
    }
}

int main()
{
    cin >> n >> m; // 输入地图行数和列数
    // memset(vis, 0, sizeof(vis)); // 初始化vis数组(注释掉了,因为在定义时已经初始化为全0)

    // 读取地图信息
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cin >> G[i][j];
        }
    }

    // 输入起点和终点坐标
    cin >> Start.x >> Start.y >> End.x >> End.y;

    ans = 0; // 初始化最短路径长度为0

    bfs(); // 执行宽度优先搜索

    cout << ans - 1 << endl; // 输出最短路径长度减1(因为vis记录的是步数,实际路径长度需要减1)

    return 0;
}

2. 长草

题目描述
小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,这四小块空地都将变为有草的小块。请告诉小明,k 个月后空地上哪些地方有草。

输入描述
输入的第一行包含两个整数 n, m。
接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。
接下来包含一个整数 k。 其中,2 ≤ n, m ≤ 1000,1 ≤ k ≤ 1000。

输出描述
输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。

示例

输入

4 5
.g...
.....
..g..
.....
2

输出

gggg.
gggg.
ggggg
.ggg.

题解

#include <iostream>
#include <queue>
using namespace std;
const int M = 1005;
struct PII
{
    int first;
    int second;
};
// C++ 有个数据类型叫 pair 上面的就可以定义为 pair<int,int> 用起来比较方便。
PII tempPair;//临时结点
char Map[M][M];
//---------图的路径搜索常用方向移动表示-------
int dx[4] = { 0,1,-1,0 };
int dy[4] = { 1,0,0,-1 };
// 两两组合形成上下左右四个方向
//      1------------------> x
//      |
//      |
//      |
//      |
//      |
//      |
//      |
//      ↓
//      y

// dx[0]=0 dy[0]=1 那么代表向下的方向

// dx[1]=1 dy[1]=0 那么代表向右的方向

// dx[2]=-1 dy[2]=0 那么代表向左的方向

// dx[3]=0 dy[3]=-1 那么代表向上的方向

int n;// n 行
int m;// m 列
int k;// k 次

queue<PII> q; //广度优先搜索所用的队列

int len;//记录节点数量方便后续k的计算
bool pd(int x, int y)
{
    if (x < 1)
        return 0;
    //x 轴坐标 左侧越界
    else if (x > n)
        return 0;
    //x 轴坐标 右侧越界
    else  if (y < 1)
        return 0;
    //y 轴坐标 上侧越界
    else if (y > m)
        return 0;
    //y 轴坐标 下侧越界
    else if (Map[x][y] == 'g')
        return 0;
    //已经长草了
    else return 1;
    //在范围内,且没长草
}

void BFS()
{
    //BFS
    while (!q.empty() && k > 0)
    {
        tempPair = q.front();
        q.pop();
        //这两步是取出队首的节点

        int x = tempPair.first;//横坐标
        int y = tempPair.second;//纵坐标

        for (int i = 0; i < 4; i++)
        {
            int nowx = x + dx[i]; //扩展后的横坐标
            int nowy = y + dy[i]; //扩展后的纵坐标

            if (pd(nowx, nowy))
            {
                q.push({ nowx,nowy });
                Map[nowx][nowy] = 'g';
            }
            //符合要求执行扩展,不符合要求,忽略即可。
        }

        len--; //每取出一个节点len-1
        if (len == 0)
        {
            //当len =0 时,代表当前层扩展完了,那么就代表第一个月扩展完了
            k--; //所以k--
            len = q.size(); //当前层扩展完了,那就该扩展下一层了,所以len又被赋值为下一层的节点数目的值
        }
    }
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cin >> Map[i][j];
            if (Map[i][j] == 'g')
            {
                tempPair.first = i;
                tempPair.second = j;
                // cout<<i<<""<<j<<endl;
                q.push(tempPair);//将初始有树的结点加入队列
            }
        }
    }

    len = q.size();//记录第一层的节点数量方便后续k的计算
    cin >> k;
    BFS();
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cout << Map[i][j];
        }

        cout << endl;
    }
    return 0;
}

三、二分法

1. 分巧克力

题目描述
儿童节那天有 K 位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。小明一共有 N 块巧克力,其中第 i 块是 H_i \times W_i 的方格组成的长方形。为了公平起见, 小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。切出的巧克力需要满足:1、形状是正方形,边长是整数;2、大小相同;
例如一块 6x5 的巧克力可以切成 6 块 2x2 的巧克力或者 2 块 3x3 的巧克力。当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少?

输入描述
第一行包含两个整数 N, K ( 1 ≤ N , K ≤ 1 0 5 1 \leq N, K \leq 10^5 1N,K105)。
接下来 N 行每行包含两个整数 H_i, W_i ( 1 ≤ H i ; W i ≤ 1 0 5 1 \leq H_i; W_i \leq 10^5 1Hi;Wi105)。
输入保证每位小朋友至少能获得一块 1x1 的巧克力。

输出描述
输出切出的正方形巧克力最大可能的边长。

示例

输入

2 10
6 5
5 6

输出

2

题解

#include<iostream>
using namespace std;
int const n = 1e5 + 5;
int H[n];
int W[n];

int main() {
    // 输入整数N(表示物品数量)和K(表示目标面积)
    int N, K;
    cin >> N >> K;

    // 读取N个物品的高度和宽度数据
    for (int i = 0; i < N; i++) {
        cin >> H[i] >> W[i];
    }

    // 初始化搜索范围上限R为20000,下限L为0
    int R = 20000;
    int L = 0;

    // 初始化累计面积ans为0
    int ans = 0;

    // 定义中间值变量mid,以及临时变量num1和num2
    int mid, num1, num2;

    // 当搜索范围不为空时(即L<R),执行二分查找过程
    while (L < R) {
        // 计算当前搜索范围的中间值
        mid = (L + R + 1) / 2;

        // 遍历所有物品,计算每个物品按当前中间值切割后的小矩形数量,并累加到ans
        for (int j = 0; j < N; j++) {
            num1 = H[j] / mid;
            num2 = W[j] / mid;
            ans += num1 * num2;
        }

        // 检查当前累计面积是否小于目标面积K,如果是,则将搜索范围上界调整为mid-1;否则,将搜索范围下界调整为mid
        if (ans < K) {
            R = mid - 1;
        } else {
            L = mid;
        }
    }

    // 输出找到的最大中间值(满足目标面积的最小分割长度)
    cout << L;
    return 0;
}

2. 跳石头

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

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

其中,0≤M≤N≤5×104;1≤L≤109

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

示例

输入

25 5 2
2
11
14
17
21

输出

4

题解

#include <iostream>
using namespace std;
int len, n, m;
int stone[50005];
//检查距离d是否合适
bool check(int d) {
    int num = 0; //num记录搬走石头的数量
    int pos = 0; //当前站立的石头
    for (int i = 1; i <= n; ++i)
        if (stone[i] - pos < d) num++; //第i块石头可以搬走
        else pos = stone[i]; //第i块石头不能搬走
    if (num <= m) return true; //要移动的石头比m少,满足条件
    else return false; //要移动的石头比m多,不满足条件
}
int main() {
    cin >> len >> n >> m;
    for (int i = 1; i <= n; ++i) cin >> stone[i]; //输入第i块石头与终点的距离
    int L = 0, R = len, mid;
    while (L < R) {
        mid = (L + R) / 2;
        if (check(mid)) L = mid + 1; //满足条件,说明mid小了,调大一点
        else R = mid - 1; //不满足条件,说明mid大了,调小一点
    }

    cout << L;
    return 0;
}

四、组合算法

1. 公平抽签

题目描述
A A A 的学校,蓝桥杯的参赛名额非常有限,只有 m m m 个名额,但是共有 n n n 个人报名。作为老师非常苦恼,他不知道该让谁去,他在寻求一个绝对公平的方式。于是他准备让大家抽签决定,即 m m m 个签是去,剩下的不去。小 A A A 非常想弄明白最后的抽签结果会有多少种不同到情况,请你设计一个程序帮帮小 A A A!

输入描述
输入第一行包含两个字符 n , m n, m n,m,其含义如题所述。
接下来第二行到第 n + 1 n+1 n+1 行每行包含一个字符串 S S S ,表示个人名。

1 ≤ m ≤ n ≤ 15 1 \leq m \leq n \leq 15 1mn15

输出描述
输出共若干行,每行包含 m m m 个字符串,表示该结果被选中到人名(需按字符串的输入顺序大小对结果进行排序)。

示例

输入

3 2
xiaowang
xiaoA
xiaoli

输出

xiaowang xiaoA
xiaowang xiaoli
xiaoA xiaoli

题解

#include<iostream>
#include<vector>
#include<string>
using namespace std;

// 定义变量:人数n、名额m、姓名列表name和路径列表path
int n, m;
vector<string>name;
vector<string>path;

// 定义递归函数dfs,用于深度优先搜索
void dfs(int i) {
    // 当路径列表长度等于名额时,输出路径并换行
    if (path.size() == m) {
        for (int j = 0; j < m; j++) cout << path[j] << " ";
        cout << endl;
        return;
    }

    // 剪枝条件:如果当前索引超过总人数或剩余人数不足以满足名额要求,则返回
    if (i > n || path.size() + n - i + 1 < m) return;

    // 将当前姓名添加到路径列表中,并递归调用dfs
    path.push_back(name[i - 1]);
    dfs(i + 1);
    path.pop_back(); // 回溯

    // 不选择当前姓名,继续尝试下一个姓名
    dfs(i + 1);
}

int main() {
    // 输入人数n、名额m和所有人的姓名
    cin >> n >> m;
    for (int k = 1; k <= n; k++) {
        string s;
        cin >> s;
        name.push_back(s);
    }

    // 调用dfs函数开始搜索
    dfs(1);

    return 0;
}

五、排列算法

1. 座次问题

题目描述
A A A 的学校,老师好不容易解决了蓝桥杯的报名问题,现在老师又犯愁了。现在有 N N N 位同学参加比赛,但是老师想给他们排座位,但是排列方式太多了。老师非常想弄明白最后的排座次的结果是什么样子的,到底有多少种结果。请设计一个程序帮助老师。最后输出各种情况的人名即可,一行一种情况,每种情况的名字按照报名即输入顺序排序。

输入描述
输入第一行包含一个整数 N N N
输入第一行包含一个整数 N N N
接下来 N N N 行每行包含一个字符串 S i S_i Si,表示人名。
接下来 N N N 行每行包含一个字符串 S i S_i Si,表示人名。

1 ≤ N ≤ 10 1 \leq N \leq 10 1N10 ∑ i = 1 N ∣ S i ∣ ≤ 1 0 2 \sum_{i=1}^{N}|S_i| \leq 10^2 i=1NSi102

输出描述
输出共若干行,每行输出各种情况的人名。一行一种情况,每种情况的名字按照报名即输入顺序排序。
输出共若干行,每行输出各种情况的人名。一行一种情况,每种情况的名字按照报名即输入顺序排序。
每行一种情况,每种情况的名字按照报名即输入顺序排序。

示例

输入

3
xiaowang
xiaoA
xiaoli

输出

xiaowang xiaoA xiaoli
xiaowang xiaoli xiaoA
xiaoA xiaowang xiaoli
xiaoA xiaoli xiaowang
xiaoli xiaowang xiaoA
xiaoli xiaoA xiaowang

题解

#include<iostream>
#include<string>
using namespace std;

// 定义一个布尔数组,用于记录每个学生是否被选中
bool chosen[15];

// 定义一个整数变量,用于存储学生数量
int N;

// 定义一个字符串数组,用于存储学生的姓名
string name[15];

// 定义一个字符串数组,用于存储学生的座位号
string seat[15];

// 定义一个递归函数,用于遍历所有可能的座位分配方案
void dfs(int k) {
    // 如果已经遍历完所有学生,输出所有可能的座位分配方案
    if (k == N + 1) {
        for (auto x: seat) cout << x << " ";
        cout << endl;
        return;
    }
    // 遍历所有未被选中的学生,将其加入到座位分配方案中
    for (int j = 1; j <= N; j++) {
        // 如果该学生已经被选中,跳过
        if (chosen[j] == 1) continue;
        // 将该学生加入到座位分配方案中
        seat[k] = name[j];
        // 标记该学生已被选中
        chosen[j] = 1;
        // 递归调用函数,处理剩余的学生
        dfs(k + 1);
        // 从座位分配方案中移除该学生
        seat[k].erase();
        // 撤销对该学生的标记
        chosen[j] = 0;
    }
}

int main() {
    // 输入学生数量
    cin >> N;
    // 输入所有学生的姓名
    for (int i = 1; i <= N; i++) {
        cin >> name[i];
    }
    // 调用递归函数,开始遍历所有可能的座位分配方案
    dfs(1);
    return 0;
}

二次创作声明
该贴参考15届蓝桥杯14天省赛冲刺营
部分结合博主自行整理的题解和注释进行整合发布,若侵权请联系删除!!!

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值