蓝桥杯复习清单
枚举
日期统计
问题描述
小蓝现在有一个长度为 100 的数组,数组中的每个元素的值都在 0 到 9 的范围之内。数组中的元素从左至右如下所示:
5 6 8 6 9 1 6 1 2 4 9 1 9 8 2 3 6 4 7 7 5 9 5 0 3 8 7 5 8 1
5 8 6 1 8 3 0 3 7 9 2
7 0 5 8 8 5 7 0 9 9 1 9 4 4 6 8 6 3 3 8 5 1 6 3 4 6 7 0 7 8
2 7 6 8 9 5 6 5 6 1 4 0 1
0 0 9 4 8 0 9 1 2 8 5 0 2 5 3 3
现在他想要从这个数组中寻找一些满足以下条件的子序列:
- 子序列的长度为 8;
- 这个子序列可以按照下标顺序组成一个 yyyymmdd 格式的日期,并且要求这个日期是 2023 年中的某一天的日期,例如 20230902, 20231223。yyyy 表示年份,mm 表示月份,dd 表示天数,当月份或者天数的长度只有一位时需要一个前导零补充。
请你帮小蓝计算下按上述条件一共能找到多少个不同的 2023 年的日期。对于相同的日期你只需要统计一次即可。
解法:
由题意可知,要找⽇期全是 2023 年份的,只有⽉和⽇会变化。因此,可以先枚举 2023 年的每⼀个⽉和⽇,然后判断序列中是否存在这个⽇期。
枚举 2023 年的每⼀个⽉和⽇的时间开销为 365,判断数组中是否存在这个⽇期的时间开销为 100,效率更⾼。
代码如下:
C++
#include <iostream>
using namespace std;
int n = 100;// 数组长度固定为 100
int a[110];// 存储输入的数字序列,下标从1开始
int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//days[0]占位不用,days[1]是 1 月的天数,依此类推
int main()
{
for (int i = 1; i <= n; i++) cin >> a[i];
int cnt = 0;
for (int m = 1; m <= 12; m++)//枚举月份
for (int d = 1; d <= days[m]; d++)//枚举天数
{
int t[] = { 2,0,2,3,m / 10,m % 10,d / 10,d % 10 };
for (int i = 0, j = 1; j <= n; j++)
{
if (a[j] == t[i])// // 当前数字匹配目标序列
{
i++;//匹配下一个数
if (i == 8)// 8 个数字全部匹配成功!
{
cnt++;// 计数器+1
break;// 跳出循环,处理下一个日期
}
}
}
}
cout << cnt << endl;
return 0;
}
二分查找
冶炼金属
【题目描述】
小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V,V 是一个正整数,这意味着消耗 V 个普通金属 O 恰好可以冶炼出一个特殊金属 X ,当普通金属 O 的数目不足 V 时,无法继续冶炼。
现在给出了 N 条冶炼记录,每条记录中包含两个整数 A 和 B,这表示本次投入了 A 个普通金属 O,最终冶炼出了 B 个特殊金属 X。每条记录都是独立的,这意味着上一次没消耗完的普通金属 O 不会累加到下一次的冶炼当中。
根据这 N 条冶炼记录,请你推测出转换率 V 的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。
【输入描述】
第一行一个整数 N,表示冶炼记录的数目。
接下来输入 N 行,每行两个整数 A、B,含义如题目所述。
【输出描述】
输出两个整数,分别表示 V 可能的最小值和最大值,中间用空格分开。
【示例一】
输入:
3
75 3
53 2
59 2
输出:
20 25
代码如下:
C++
#include <iostream>
using namespace std;
const int N = 1e4 + 10;
int n, a[N], b[N];
bool check1(int v)
{
for (int i = 1; i <= n; i++)
if (a[i] / v > b[i])
return false;
return true;
}
bool check2(int v)
{
for (int i = 1; i <= n; i++)
if (a[i] / v < b[i])
return false;
return true;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i] >> b[i];
//二分最小值
int l = 1, r = 1e9;
while (l < r)
{
int mid = (l + r) / 2;
if (check1(mid)) r = mid;
else l = mid + 1;
}
cout << l << " ";
//二分最大值
l = 1, r = 1e9;
while (l < r)
{
int mid = (l + r + 1) / 2;//mid = (l + r + 1) / 2是向上取整,避免在 l=r-1 时死循环。
if (check2(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
return 0;
}
DFS
飞机降落
【题目描述】
N架飞机准备降落到某个只有一条跑道的机场。其中第 i架飞机在 Ti时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 Di个单位时间,即它最早可以于 Ti时刻开始降落,最晚可以于 Ti+Di时刻开始降落。降落过程需要 Li个单位时间。
一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。
请你判断 N架飞机是否可以全部安全降落。
【输入描述】
输入包含多组数据。
第一行包含一个整数 T,代表测试数据的组数。
对于每组数据,第一行包含一个整数 N。
以下 N行,每行包含三个整数:Ti,Di和 Li。
【输出描述】
对于每组数据,输出 YES或者 NO,代表是否可以全部安全降落。
【示例一】
输入:
2
3
0 100 10
10 10 10
0 2 20
3
0 10 20
10 10 20
20 10 20
输出:
YES
NO
代码如下:
C++
#include <iostream>
#include <cstring>
using namespace std;
const int N = 15;
int n, t[N], d[N], l[N];
bool st[N];
bool dfs(int pos, int end)
{
//pos:当前要安排第几架飞机(已安排了 pos-1 架)
//end:当前跑道的可用时间(上一架飞机降落结束的时间)
if (pos > n) return true;// 所有飞机都安排好了
for (int i = 1; i <= n; i++)
{
if (st[i]) continue;
//如果最晚开始时间< 跑道可用时间,那么飞机无法降落(油料不够等到跑道空闲)
if (t[i] + d[i] < end) continue;
st[i] = true;
int newend = max(t[i], end) + l[i];
if (dfs(pos + 1, newend)) return true;
st[i] = false;
}
return false;
}
int main()
{
int T;
cin >> T;
while (T--)
{
memset(st, 0, sizeof st);
cin >> n;
for (int i = 1; i <= n; i++) cin >> t[i] >> d[i] >> l[i];
if (dfs(1, 0)) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
线性DP-最长递增子序列
接龙数列
问题描述
对于一个长度为 K 的整数数列:A₁, A₂, ..., Aₖ,我们称之为接龙数列当且仅当 Aᵢ 的首位数字恰好等于 Aᵢ₋₁ 的末位数字(2 ≤ i ≤ K)。例如 12, 23, 35, 56, 61, 11 是接龙数列;12, 23, 34, 56 不是接龙数列,因为 56 的首位数字不等于 34 的末位数字。所有长度为 1 的整数数列都是接龙数列。
现在给定一个长度为 N 的数列 A₁, A₂, ..., Aₙ,请你计算最少从中删除多少个数,可以使剩下的序列是接龙序列?
输入格式
第一行包含一个整数 N。
第二行包含 N 个整数 A₁, A₂, ..., Aₙ。
输出格式
一个整数代表答案。
样例输入
5
11 121 22 12 2023
样例输出
1
样例说明
删除 22,剩余 11, 121, 12, 2023 是接龙数列。
代码如下:
C++
#include <iostream>
#include <string>
using namespace std;
const int N = 1e5 + 10;
int n;
int len[10]; // len[i] 表示:以数字 i 结尾的接龙序列的最大长度
int main() {
cin >> n;
int ret = 0; // 记录全局最长的接龙序列长度
for (int i = 1; i <= n; i++) {
string s;
cin >> s;
int l = s[0] - '0'; // 首位数字
int r = s.back() - '0'; // 末位数字
int t = len[l] + 1; // 当前数字可以接在以 l 结尾的序列后面
len[r] = max(len[r], t); // 更新以 r 结尾的序列最大长度
ret = max(ret, t); // 更新全局最大值
}
cout << n - ret << endl; // 最少删除数 = 总数 - 最长接龙序列长度
return 0;
}
FloodFill问题
岛屿个数
问题描述
小蓝得到了一副大小为 M × N 的格子地图,可以将其视作一个只包含字符 '0'(代表海水)和 '1'(代表陆地)的二维数组,地图之外可以视作全部是海水,每个岛屿由在上/下/左/右四个方向上相邻的 '1' 相连接而形成。
在岛屿 A 所占据的格子中,如果可以从中选出 k 个不同的格子,使得他们的坐标能够组成一个这样的排列:(x₀, y₀), (x₁, y₁), ..., (xₖ₋₁, yₖ₋₁),其中 (xᵢ₊₁ mod k, yᵢ₊₁ mod k) 是由 (xᵢ, yᵢ) 通过上/下/左/右移动一次得来的(0 ≤ i ≤ k - 1),此时这 k 个格子就构成了一个"环"。如果另一个岛屿 B 所占据的格子全部位于这个"环"内部,此时我们将岛屿 B 视作是岛屿 A 的子岛屿。若 B 是 A 的子岛屿,C 又是 B 的子岛屿,那 C 也是 A 的子岛屿。
请问这个地图上共有多少个岛屿?在进行统计时不需要统计子岛屿的数目。
输入格式
第一行一个整数 T,表示有 T 组测试数据。
接下来输入 T 组数据。对于每组数据,第一行包含两个用空格分隔的整数 M、N 表示地图大小;接下来输入 M 行,每行包含 N 个字符,字符只可能是 '0' 或 '1'。
输出格式
对于每组数据,输出一行,包含一个整数表示答案。
样例输入
2
5 5
01111
11001
10101
10001
11111
5 6
111111
100001
010101
100001
111111
样例输出
1
3
代码如下:
C++
#include <iostream>
#include <cstring>
using namespace std;
const int N = 55;
int n, m, cnt;
int g[N][N];
bool st[N][N];
// 4方向:上下左右(用于岛屿标记)
int dx4[] = {-1, 1, 0, 0};
int dy4[] = {0, 0, -1, 1};
// 8方向:包括对角线(用于海水淹没)
int dx8[] = {-1, 1, 0, 0, -1, -1, 1, 1};
int dy8[] = {0, 0, -1, 1, -1, 1, -1, 1};
// 标记整个岛屿(4连通)
void dfs_mark_island(int a, int b) {
st[a][b] = true;
for (int i = 0; i < 4; i++) {
int x = a + dx4[i], y = b + dy4[i];
if (x < 1 || x > n || y < 1 || y > m || st[x][y]) continue;
if (g[x][y]) dfs_mark_island(x, y);
}
}
// 海水淹没(8连通)
void dfs_flood(int a, int b) {
st[a][b] = true;
for (int i = 0; i < 8; i++) {
int x = a + dx8[i], y = b + dy8[i];
if (x < 0 || x > n + 1 || y < 0 || y > m + 1 || st[x][y]) continue;
if (g[x][y]) { // 遇到陆地
cnt++; // 这是一个新岛屿
dfs_mark_island(x, y); // 标记整个岛屿,防止重复计数
} else {
dfs_flood(x, y); // 继续淹没海水
}
}
}
int main() {
int T;
cin >> T;
while (T--) {
// 初始化
memset(g, 0, sizeof g);
memset(st, 0, sizeof st);
cnt = 0;
cin >> n >> m;
// 读入地图
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
char ch;
cin >> ch;
g[i][j] = ch - '0';
}
}
// 从地图外(0,0)开始海水淹没
// 注意:g[0][0]一定是0(海水),因为地图外都是海水
dfs_flood(0, 0);
cout << cnt << endl;
}
return 0;
}
模拟
产值调整
问题描述
偏远的小镇上,三兄弟共同经营着一家小型矿业公司"兄弟矿业"。公司旗下有三座矿山:金矿、银矿和铜矿,它们的初始产值分别用非负整数 A、B和 C表示。这些矿山的产出是小镇经济的核心,支撑着三兄弟和许多矿工家庭的生计。
然而,各矿山的产值波动剧烈,有时金矿收益高而银矿、铜矿低迷,有时则相反。这种不稳定性让公司收入难以预测,也常引发兄弟间的争执。为了稳定经营,三兄弟设计了一个公平的产值调整策略,每年执行一次,每次调整时,将根据当前的产值 A、B、C,计算新产值:
- 金矿新产值 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次调整后,金矿、银矿和铜矿的产值分别是多少。
输入格式
输入的第一行包含一个整数 T,表示测试用例的数量。
接下来的 T行,每行包含四个整数 A,B,C,K,分别表示金矿、银矿和铜矿的初始产值,以及需要执行的调整次数。
输出格式
对于每个测试用例,输出一行,包含三个整数,表示经过 K次调整后金矿、银矿和铜矿的产值,用空格分隔。
样例输入
2
10 20 30 1
5 5 5 3
样例输出
25 20 15
5 5 5
代码如下:
C++
include <iostream>
using namespace std;
int main()
{
int T; cin >> T;
while(T--)
{
int a, b, c, k; cin >> a >> b >> c >> k;
while(k--)
{
int x = (b + c) / 2, y = (a + c) / 2, z = (a + b) / 2;
a = x, b = y, c = z;
if(a == b && a == c) break;
}
cout << a << " " << b << " " << c << endl;
}
}
01BFS
水质检测
问题描述
小明需要在一条 2×n的河床上铺设水质检测器。在他铺设之前,河床上已经存在一些检测器。如果两个检测器上下或左右相邻,那么这两个检测器就是互相连通的。
连通具有传递性,即如果 A和 B连通,B和 C连通,那么 A和 C也连通。现在他需要在河床上增加铺设一些检测器,使得所有检测器都互相连通。他想知道最少需要增加铺设多少个检测器?
输入格式
输入共两行,表示一个 2×n的河床。
每行一个长度为 n的字符串,仅包含 #和 .,其中 #表示已经存在的检测器,.表示空白。
输出格式
输出共 1 行,一个整数,表示最少需要增加的检测器数量。
样例输入
.##....#
.#..#...
样例输出
5
样例说明
其中一种方案:###....#.#######
增加了 5 个检测器。
代码如下:
C++
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1e6 + 10;
int n;
char g[2][N];
int dist[2][N];
bool st[2][N];
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
int bfs(int i, int j) {
int ret = 0;
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st); // 每次BFS需要重置st数组!
deque<pair<int, int>> q;
q.push_back({i, j});
dist[i][j] = 0;
while(q.size()) {
auto t = q.front();
q.pop_front();
int a = t.first, b = t.second;
if(st[a][b]) continue;
st[a][b] = true;
if(g[a][b] == '#')
ret = max(ret, dist[a][b]);
for(int k = 0; k < 4; k++) {
int x = a + dx[k], y = b + dy[k];
if(x < 0 || x >= 2 || y < 0 || y >= n) continue;
int w = (g[x][y] == '#') ? 0 : 1;
if(dist[a][b] + w < dist[x][y]) {
dist[x][y] = dist[a][b] + w;
if(w == 0)
q.push_front({x, y});
else
q.push_back({x, y});
}
}
}
return ret;
}
int main() {
cin >> g[0] >> g[1];
n = strlen(g[0]);
// 找到第一个已有的检测器作为起点
for(int j = 0; j < n; j++) {
if(g[0][j] == '#') {
cout << bfs(0, j) << endl;
return 0;
}
if(g[1][j] == '#') {
cout << bfs(1, j) << endl;
return 0;
}
}
// 如果没有检测器
cout << 0 << endl;
return 0;
}