题目目录
一、深度遍历算法
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
1≤N,K≤105)。
接下来 N 行每行包含两个整数 H_i, W_i (
1
≤
H
i
;
W
i
≤
1
0
5
1 \leq H_i; W_i \leq 10^5
1≤Hi;Wi≤105)。
输入保证每位小朋友至少能获得一块 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 1≤m≤n≤15。
输出描述
输出共若干行,每行包含
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 1≤N≤10, ∑ i = 1 N ∣ S i ∣ ≤ 1 0 2 \sum_{i=1}^{N}|S_i| \leq 10^2 ∑i=1N∣Si∣≤102。
输出描述
输出共若干行,每行输出各种情况的人名。一行一种情况,每种情况的名字按照报名即输入顺序排序。
输出共若干行,每行输出各种情况的人名。一行一种情况,每种情况的名字按照报名即输入顺序排序。
每行一种情况,每种情况的名字按照报名即输入顺序排序。
示例
输入
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天省赛冲刺营
部分结合博主自行整理的题解和注释进行整合发布,若侵权请联系删除!!!