【蓝桥杯】第十三届蓝桥杯省赛 AK 攻略 —— C++ B组全真题超详细剖析

news/2024/5/19 19:45:33/文章来源:https://blog.csdn.net/qq_51439643/article/details/124548511

🎄目录

  • 🌼写在前面
    • 🌻 A题 --- 九进制转十进制
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
    • 🌻 B题 --- 顺子日期
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
    • 🌻 C题 --- 刷题统计
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
    • 🌻 D题: 修剪灌木
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
    • 🌻 E题:X进制减法
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
    • 🌻 F题:统计子矩阵
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
    • 🌻 G题:积木画
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
    • 🌻 H题:扫雷
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
    • 🌻 I题:李白打酒加强版
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
    • 🌻 J题:砍竹子
      • 🌷 题目描述
      • 🌷 解题思路
      • 🌷 代码编写
  • 💗写在最后


🌼写在前面

Hello朋友们😋,我是秋刀鱼🐟,一只活跃于Java区与算法区的新人博主~

image-20200916114846002

欢迎大家加入高校算法学习社区🏰, 社区里大佬云集,大家互相交流学习!


蓝桥杯的成绩已经公布,看到很多朋友拿了奖秋刀鱼很是高兴!大家都是好样的!因为疫情缘故秋刀鱼的蓝桥杯比赛被推迟到了 5 月 14 日第二批,因此还有半个月的时间进行准备。今天呢给大家带来蓝桥杯 C++ B组真题解析,这套题目也是耗费了我将近 7 个小时的时间才AK,希望看完能让你有所收获。如果觉得博主写的还不错的话务必三连支持一下:


🎉🎉主页:秋刀鱼与猫🎉🎉
🎉🎉期待你的支持与关注~🎉🎉

🌻 A题 — 九进制转十进制

🌷 题目描述

image-20220501205543893

🌷 解题思路

很简单一道进制转换题目!这可不能做错哦!

🌷 代码编写

#include <iostream>
#include <cmath> 
using namespace std;int main() {cout << 2 * pow(9, 0) + 2 * pow(9, 1) + 0 * pow(9, 2) + 2 * pow(9, 3) << endl;return 0;
}

🌻 B题 — 顺子日期

🌷 题目描述

image-20220501205839184

🌷 解题思路

这道题实现起来还算简单,但是题目中的说明有点模棱两可,012到底能不能作为顺子呢?最后官方说明两个答案都算正确。

🌷 代码编写

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int months[] = {0, 31, 28, 31, 30, 31,30, 31, 31, 30, 31, 30,31
};bool check(string str)
{for (int i = 0; i + 2 < str.size(); i ++ )if (str[i + 1] == str[i] + 1 && str[i + 2] == str[i] + 2)return true;return false;
}int main()
{int year = 2022, month = 1, day = 1;int res = 0;for (int i = 0; i < 365; i ++ ){char str[10];sprintf(str, "%04d%02d%02d", year, month, day);if (check(str)){res ++ ;cout << str << endl;}if ( ++ day > months[month]){day = 1;month ++ ;}}cout << res << endl;return 0;
}

🌻 C题 — 刷题统计

🌷 题目描述

题传送门

在这里插入图片描述

🌷 解题思路

计算出从周一开始刷题一周的刷题数量 weekCostweekCostweekCost ,将 n/weekCostn/weekCostn/weekCost 获取到需要多少个周,更新答案。获取剩余的题目,剩余的题目一定能在一周之内完成,继续枚举获取答案值。非常简单!

🌷 代码编写

#include <iostream>
#define ll long long
using namespace std;int main() {ll a, b, n;cin >> a >> b >> n;ll weekCost = a * 5 + b * 2;ll ans = 0;ans += (n / weekCost) * 7;n %= weekCost;for (int i = 1; n > 0 && i <= 7; ++i,++ans) {if (i <= 5) {n -= a;}else {n -= b;}}cout << ans;return 0;
}

🌻 D题: 修剪灌木

🌷 题目描述

题目传送门

在这里插入图片描述

🌷 解题思路

解决本题的关键是判断每棵灌木最高的高度,简单地思考不难发现该高度与该点距离左、右侧边界的距离相关。定义左侧边界距离为 iii ,则右侧边界可计算出为:n−i−1n-i-1ni1,如下图所示:

在这里插入图片描述

灌木的最高高度就是:max{i,n−i−1}∗2max\{i,n-i-1\} * 2max{i,ni1}2

🌷 代码编写

#include <iostream>
#include <math.h>
#define ll long long
using namespace std;
int main() {int n;cin >> n;for (int i = 0; i < n; ++i) {cout << max(i , (n - i - 1)) * 2;cout << endl;}return 0;
}

🌻 E题:X进制减法

🌷 题目描述

题目传送门

在这里插入图片描述

🌷 解题思路

理解题意

我相信很多朋友不会做这道题是因为题目的意思没有理清楚。其实题目的要求很简单:给定了两个整数 A,B,这两个数每一位上可以是任意进制,但是A,B相同位上进制规则相同。举个栗子:

image-20220430124942685

若 X,Y,Z 分别代表 5,6,2 进制,则 A = 4*6*2 + 2*6 + 1 ,B = 3*6*2 + 5*6 + 0

如 X,Y,Z 分别代表 6,7,3 进制,则 A = 4*7*3 + 2*7 + 1 ,B = 3*7*3 + 5*7 + 0

无论每一位进制为何值,都需要满足:

  • 进制数要大于改位的最大值
  • 进制数的最小值为 2

满足上述要求的每一位进制所得到的数:A,B ,题目要求获取 A - B 的最小值。

解题方法

首先给出结论:只需要将每一位进制数设置为最小值, A - B 的值一定是最小值。

结论证明

首先假设 A 的每一位值为 AiA_iAi,最低位的值为 A1A_1A1 ,最高位的值为 AnA_nAn ,B 同理。同时假设每一位的进制为 SiS_iSi 最低位进制为 S1S_1S1 ,最高位进制为 SnS_nSn。下面以 n = 4 为例进行证明:

经过推导不难发现 A-B 的值能进行如下的表示:

image-20220430130906401

先考虑箭头所指位置:题目中给定了 A−B>0A - B >0AB>0 ,因此 A4−B4>=0A_4 - B_4 >= 0A4B4>=0 恒成立, 为了使结果值最小,因此 S3S_3S3 的值应当最小。

image-20220430131425558

继续看箭头所指的位置,因为 S3S_3S3 是第三位的进制数,因此 S3>A3S_3 > A_3S3>A3S3>B3S_3 > B3S3>B3 ,而 A4−B4>=0A_4 - B_4 >=0A4B4>=0,因此 S3(A4−B4)>=(A3−B3)S_3(A_4-B_4) >= (A_3-B_3)S3(A4B4)>=(A3B3),因此方框位置的值仍为正数或 0 ,为了使该值最小, S2S_2S2 应当取最小值。

继续证明的思路与上述思路相同,通过证明最终得出:为了使 A−BA-BAB 的值最小,每一位的表示的进制数应当最小!

image-20200916114846002

🌷 代码编写

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 1000000007;
int main() {int N, ma, mb;int nums[100001];ll ans = 0;memset(nums, 0, sizeof nums);cin >> N;cin >> ma;for (int i = 0; i < ma; ++i) {cin >> nums[i];}cin >> mb;for (int i = 0; i < ma; ++i) {int val = 0;if (mb + i >= ma) {cin >> val;}// 必须满足进制数大于 2 这个前提ans *= max(max(nums[i], val) + 1, 2);ans += (nums[i] - (ll)val);ans %= mod;}cout << ans;return 0;
}

🌻 F题:统计子矩阵

🌷 题目描述

题目传送门

image-20220430220905834

【评测用例规模与约定】

对于 30% 的数据,N, M ≤ 20.

对于 70% 的数据,N, M ≤ 100.

对于 100% 的数据,1 ≤ N, M ≤ 500; 0 ≤ *Ai j* ≤ 1000; 1 ≤ K ≤ 250000000**

🌷 解题思路

二维前缀和

对于任意一个矩阵,如何快速获取到任意一个字矩阵中所有数的和呢?可以使用二维前缀和的思想。定义一个二维前缀和数组 sum[n][m]sum[n][m]sum[n][m] 记录前缀和,sum[i][j]sum[i][j]sum[i][j] 记录 (0,0)−>(i,j)(0,0) -> (i,j)(0,0)>(i,j) 矩阵中的数之和,就如下图所示:

在这里插入图片描述

获取任意一个子矩阵 (a,b)−>(i,j)(a,b) - >(i,j)(a,b)>(i,j) 矩阵数之和等于 :sum[i][j]−sum[a−1][j]−sum[i][b−1]+sum[a−1][b−1]sum[i][j] - sum[a-1][j] - sum[i][b-1] + sum[a-1][b-1]sum[i][j]sum[a1][j]sum[i][b1]+sum[a1][b1] 如下图所示:

在这里插入图片描述

只需要预先处理好前缀和数组,按照上述方式就能够在O(1)O(1)O(1)的时间复杂度下求出任意子矩阵的数之和。

二分查找

不妨我们将遍历每一个点,将遍历到的点作为矩阵左上侧的点固定。同时遍历剩余的点,遍历得到右下侧的点通过这两个点确定一个矩形,再根据二维前缀和得到该矩形数之和即可获得答案,但是这样的时间复杂度是O(N4)O(N^4)O(N4)对于 500 的数据量可能会超时,有什么更好的方法呢?

image-20200916114846002

其实使用二分就可以解决上面的问题,具体操作如下:

固定了左上角点后遍历每一行时,因为从左到右遍历生成的子矩阵其数之和一定是递增的(因为拿取了越来越多的数),因此只需要对每一行的数据使用二分查找该行形成的子矩阵的数据,找到临界值点即可,如下图所示:

在这里插入图片描述

在这里插入图片描述

满足: (i,j)−>(z,l)(i,j) ->(z,l)(i,j)>(z,l) 矩阵数之和小于等于 K,而对于 (i,j)−>(z,l+1)(i,j)->(z,l+1)(i,j)>(z,l+1) 矩阵数之和一定大于 K,因此能够通过二分查找的方式查找搜索每一行来找到该临界值 lll ,此时可以获得的子矩阵数量为 l−j+1l-j+1lj+1,可以将时间复杂度优化为 O(N3⋅lgN)O(N^3\cdot lgN)O(N3lgN)

继续优化

虽然说上述的方法已经能够极大地降低时间复杂度,但是还是无情地超时了:

在这里插入图片描述

image-20200916114846002

还需要进一步的优化:

还是上述的解法,只不过固定左上角点 (i,j)(i,j)(i,j) 后枚举每一行时,按照 z 下标从大到小的方式枚举:

在这里插入图片描述

还是上述的情况,在 zzz 行枚举到 lll 值之后,不难发现蓝色区域内以(i,j)(i,j)(i,j)作为左上角点的子矩阵数量是能够计算出来的,该值就是蓝色区域内的方块数目 (l−j+1)⋅(z−i+1)(l-j+1)\cdot (z-i+1)(lj+1)(zi+1) ,直接将该值更新到答案中。按照下标从大到小的顺序遍历因此下一次遍历的行是 z−1z-1z1 行,因为蓝色区域中包含了 z−1z-1z1 行的一部分,而这一部分已经被更新到了答案之中。因此该二分查找的范围有所变化,二分查找的左边界只需要定为 l+1l+1l+1 ,如下图所示:

在这里插入图片描述

这样通过压缩二分查找的左边界范围而减少二分查找次数,最终 AC 这道题。

在这里插入图片描述

image-20200916114846002

🌷 代码编写

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 510;
ll sum[N][N];
int main() {ll n, m, k;memset(sum, 0L, sizeof sum);cin.tie();cin >> n >> m >> k;ll ans = 0;// 处理前缀和for (int i = 1; i <= n; i++){for (int j = 1; j <= m; ++j) {int val;cin >> val;sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + val;}}// 枚举每一个左上角位置for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) {int pre = j - 1;// 遍历行for (int z = n; z >= i; --z) {// 二分查找的左边界根据上一次的临界值得到int l = pre + 1;int r = m + 1;// 二分判断while (l < r) {int mid = (l + r) / 2;ll val = sum[z][mid] - sum[i - 1][mid] - sum[z][j - 1] + sum[i - 1][j - 1];if (val <= k) {l = mid + 1;}else {r = mid;}}if (l > pre + 1) {ans += (z - i + 1) * (l - pre - 1);pre = l - 1;}}}}cout << ans;return 0;
}

🌻 G题:积木画

🌷 题目描述

题传送门

在这里插入图片描述

写在前面

这道题我有在网上去搜索了一下其他博主的题解,因为我实在无法理解 dp[i]=dp[i−1]⋅2+dp[i−3]dp[i] = dp[i-1]\cdot2+dp[i-3]dp[i]=dp[i1]2+dp[i3] 这个状态转移方程是如何得到的(可能是自己太笨了),他们的题解大多都是草草两句收尾讲的有些含糊不清,作为菜狗的我真的很难懂啊啊啊啊!在思考良久后对于这道题我想到了一种自己的解题想法,与网上主流的状态转移方程不同不过同样能够解题。

image-20200916114846002

🌷 解题思路

状态表示

本题解题的关键是使用动态规划,定义一维数组 dpdpdpdp[i]dp[i]dp[i] 存储画布的大小为 2×i2\times i2×i 时积木的填充方法数。

状态初始化

不难发现:

  • 当画布大小为 2×12\times 12×1 时,只能放下一个 I 型积木,因此 dp[1]=1dp[1] =1dp[1]=1

在这里插入图片描述

  • 当画布大小为 2×22\times 22×2 时,只能放下两个 I 型积木,但积木可以旋转,如下图所示,因此 dp[2]=2dp[2] = 2dp[2]=2

    image-20220430231321372image-20220430231352164

  • 当画布大小为 2×32\times32×3时,如题目中所示,共有五种方式,因此 dp[3]=5dp[3] = 5dp[3]=5

情况梳理

梳理状态转移方程之前,不妨思考下,积木画出现在最后且符合题意的积木块只能出现哪几种情况呢?

  • 情况一:单独使用 I 型积木拼接

    完全可以使用单独的在这里插入图片描述拼接在任意一个符合题意的积木画尾部中,使积木画总长度 + 1。

  • 情况二使用两个 I 型积木拼接

    使用image-20220430231321372同样能够拼接到任意一个符合题意的积木画尾部,使其积木画总长度 + 2。

  • 情况三使用两个 L 型积木拼接

    使用在这里插入图片描述在这里插入图片描述 拼接到任意一个符合题意的积木画尾部,使其积木画总长度 +3,但要注意此时存在这两种 L 型积木拼接方式属于不同的方式,因此需要单独计算!

  • 情况四使用两个 L 型积木与 i(1,2,3,....n)i(1,2,3,....n)i(1,2,3,....n) 个 I 型积木拼接

    最容易被忽略的情况!使用 I 型积木与 两个 L 型积木同样能完成拼接。

    image-20220430233813672

    不要忘记翻转后的情况:

    image-20220501194747997

状态转移方程

  • 情况一单独使用 I 型积木拼接,dp[i]+=dp[i−1]dp[i] += dp[i-1]dp[i]+=dp[i1]

  • 情况二使用两个 I 型积木拼接,dp[i]+=dp[i−2]dp[i]+=dp[i-2]dp[i]+=dp[i2]

  • 情况三使用两个 L 型积木拼接,dp[i]+=2⋅dp[i−3]dp[i]+=2\cdot dp[i-3]dp[i]+=2dp[i3]

  • 情况四:情况四只有 i>=4i>=4i>=4 时才可能出现,具体讨论如下:

    • i==4i==4i==4 ,只存在长度为 4 的拼接情况,假设 dp[0]=1dp[0]=1dp[0]=1,也就是说 dp[4]+=dp[0]⋅2dp[4]+=dp[0]\cdot 2dp[4]+=dp[0]2
    • i==5i==5i==5,存在长度为 4,54,54,5 的拼接情况,即 dp[5]+=(dp[0]+dp[1])⋅2dp[5] += (dp[0]+dp[1])\cdot 2dp[5]+=(dp[0]+dp[1])2
    • i==6i ==6i==6,继续推导有: dp[6]+=(dp[0]+dp[1]+dp[2])⋅2dp[6]+=(dp[0]+dp[1]+dp[2])\cdot 2dp[6]+=(dp[0]+dp[1]+dp[2])2
    • i==7i==7i==7,推导有: dp[7]+=(dp[0]+dp[1]+dp[2]+dp[3])⋅2dp[7]+=(dp[0]+dp[1]+dp[2]+dp[3])\cdot 2dp[7]+=(dp[0]+dp[1]+dp[2]+dp[3])2

    按照上述推导,不难发现 dp[i]+=(dp[0,1,2,...,i−4])⋅2dp[i]+=(dp[0,1,2,...,i-4])\cdot 2dp[i]+=(dp[0,1,2,...,i4])2,因此可以定义一个变量 sumsumsum 存储 dp[0,1,2,..i]dp[0,1,2,..i]dp[0,1,2,..i] 的值,在 iii 增大的同时更新 sumsumsum 的值,初始情况 sum=0sum=0sum=0

返回结果


最终 dp[N]dp[N]dp[N] 就是题目所求值,这道题就这样解决了。

需要注意:下面代码中仅使用 a1,a2,a3a1,a2,a3a1,a2,a3 分别代表 dp[i−1],dp[i−2],dp[i−3]dp[i-1],dp[i-2],dp[i-3]dp[i1],dp[i2],dp[i3]

🌷 代码编写

#include <iostream>
#include <math.h>
#define ll long long
// #include <bits/stdc++.h>
using namespace std;
const int MOD = 1000000007;
int main() {ll n, a1, a2, a3;a1 = 5;a2 = 2;a3 = 1;cin >> n;ll sum = 0;for (ll i = 4; i <= n; ++i) {ll val = 0;val += a1;val += a2;val += (a3 * 2L);if (i >= 4) {val += sum * 2 + 2;sum += a3;}val %= MOD;a3 = a2;a2 = a1;a1 = val;}cout << a1;return 0;
}

🌻 H题:扫雷

🌷 题目描述

题传送门

在这里插入图片描述

写在前面

个人觉得本题是第十三届蓝桥杯 C++ B组最难的一道题目,常规思路最多能骗骗分,如果需要AC本道题目需要使用特殊的优化。因此整体难度偏高。

image-20200916114846002

🌷 解题思路

核心思路

我的解题思路是搜索算法 + 哈希散列,如果直接使用BFS、DFS算法搜索可能会导致超时,这里我将每一个二维的坐标通过哈希值散列为一维数组的一个索引值,缩短搜索的时间。

哈希散列

如果有学习过Java的朋友可能了解过 HashMap 的底层源码,其核心是根据传入的 Key 计算出其哈希值,并将哈希值通过散列算法散列到桶数组中,并使用链表+红黑树的存储结构解决哈希冲突。

本题中给定了炸雷的坐标 (x,y)(x,y)(x,y) ,我们也可以定义一个哈希算法根据 x,yx,yx,y 的值将其转换为哈希值。通过该哈希值就能够判断出 (x,y)(x,y)(x,y) 坐标是否出现。因为 x,yx,yx,y 的取值为 [0,109][0,10^9][0,109],因此不难得到下面的哈希算法:

// 获取每个点的哈希值
ll hashCode(int x, int y) {return x * 1e9+100L + y;
}

现在获取到了哈希值,这是一个非常大的数,如果使用一个数组存储所有哈希值是否出现肯定是不可取的。

这里就需要使用到哈希散列,操作方法:定义一个散列数组 hashUsedhashUsedhashUsed,将哈希值映射到 hashUsedhashUsedhashUsed 一个索引位置。

现在假设发射了一枚排雷火箭,通过遍历所有其爆炸范围内的点 (xi,yi)(x_i,y_i)(xi,yi) ,判断其散列到 hashUsedhashUsedhashUsed 数组的索引位是否有值来判断该点是否为炸雷。

哈希冲突

哈希散列不可避免哈希冲突,解决哈希冲突的方式也有很多,这里我使用的是 线性探测法如果哈希散列索引位置值已经存在,则在原来索引的基础上往后加一个单位,直至不发生哈希冲突。具体实现参考代码注释。

🌷 代码编写

#include <iostream>
#include <string.h>
#define ll long long
using namespace std;
const int N = 5e4, M = 8e6;
const ll MAX = 1e9+100;
struct point
{int x, y, r;
}points[N];
// 保存哈希散列情况
ll hashUsed[M];
// 散列值到points下标信息
int id[M];
// 保存炸弹是否被引爆
int used[M];// 获取每个点的哈希值
ll hashCode(int x, int y) {return x * MAX + y;
}int Dist(int x, int y, int i, int j) {return (x - i) * (x - i) + (y - j) * (y - j);
}
// 哈希散列函数
int getPos(int x, int y) {ll hash = hashCode(x, y);// 散列化哈希codeint pos = (hash % M + M) % M;// 解决哈希冲突while (hashUsed[pos] != -1 && hashUsed[pos] != hash) {// 遇上边界,继续加一会越界,因此将pos循环到0索引位置if (++pos == M) {pos = 0;}}return pos;
}
// dfs搜索,(x,y,r)为炸弹信息,bomb 代表该点是否为 炸雷
void dfs(int x, int y, int r, bool bomb) {if(bomb) {used[getPos(x, y)] = 1;}// 遍历爆炸范围内所有的点for (int i = x - r; i <= x + r; ++i) {for (int j = y - r; j <= y + r; ++j) {if (Dist(x, y, i, j) <= r * r) {int pos = getPos(i, j);// 该位置是一个炸雷且没有被引爆if (!used[pos] && id[pos]) {dfs(i, j, points[id[pos]].r, true);}}}}
}
int main() {int n, m, ret;ret = 0;cin.tie();cin >> n >> m;memset(used, 0, sizeof used);// 初始为 -1memset(hashUsed, -1, sizeof hashUsed);// 将炸雷散列后存储for (int i = 1; i <= n; ++i) {int x, y, r;cin >> x >> y >> r;points[i] = { x,y,r };ll hash = hashCode(x, y);int pos = getPos(x, y);// 散列的位置值为该位置的哈希值hashUsed[pos] = hash;// 建立对应关系id[pos] = i;}while (m--) {int x, y, r;cin >> x >> y >> r;dfs(x,y,r,false);}for (int i = 1; i <= n; ++i) {int pos = getPos(points[i].x, points[i].y);// 该位置的炸雷被引爆if (used[pos]) {++ret;}}cout << ret;return 0;
}

🌻 I题:李白打酒加强版

🌷 题目描述

题传送门

在这里插入图片描述

🌷 解题思路

状态表示

本题的解题核心是动态规划,定义三维数组 dpdpdp 存储状态,dp[i][j][k]dp[i][j][k]dp[i][j][k] 表示路上遇到了 iii 次店,jjj 次花后,酒壶中酒剩余 kkk 斗的方法数。

状态初始化

初始情况李白没有遇到店、花,初始状态下酒壶中有 2 斗,因此定义 dp[0][0][2]=1dp[0][0][2]=1dp[0][0][2]=1 为初始状态。

状态转移

  • 如果当前状态遇到一次店,即 i>0i>0i>0 且 剩余酒的数量应该是之前的两倍,即 k%2==0k\%2==0k%2==0 ,此时 dp[i][j][k]+=dp[i−1][j][k/2]dp[i][j][k]+=dp[i-1][j][k/2]dp[i][j][k]+=dp[i1][j][k/2]
  • 如果当前状态遇到一次花,即 j>0j>0j>0,此时 dp[i][j][k]+=dp[i][j−1][k+1]dp[i][j][k]+=dp[i][j-1][k+1]dp[i][j][k]+=dp[i][j1][k+1],k+1 表示酒被喝掉了一斗。

返回结果

因为最后一次遇到的一定是花且剩余酒的数量为 0 ,不妨将状态转换为:共遇到 nnn 次店,m−1m-1m1 次花且剩余酒的数量为 1,这样返回 dp[n][m−1][1]dp[n][m-1][1]dp[n][m1][1] 就是题目所求。轻松拿下!

image-20200916114846002

🌷 代码编写

#include <iostream>
#include <string.h>
#define ll long long
const int M = 110;
ll dp[M][M][M];
const int mod = 1000000007;
using namespace std;
int main(){// 0 表示花 1 表示店memset(dp, 0, sizeof dp);dp[0][0][2] = 1;int n, m;cin >> n >> m;for (int i = 0; i <= n; ++i) {for (int j = 0; j <= m; ++j) {for (int k = 0; k <= 101; ++k) {if (i == 0 && j == 0) {continue;}else {// 店if (i > 0 && !(k & 1)) {dp[i][j][k] += dp[i - 1][j][k / 2];}// 花if (j > 0) {dp[i][j][k] += dp[i][j - 1][k + 1];}dp[i][j][k] %= mod;}}}}cout << dp[n][m - 1][1];return 0;
}

🌻 J题:砍竹子

🌷 题目描述

题传送门

在这里插入图片描述

🌷 解题思路

解题思路

本题的解法有很多,这里我使用的是优先队列 + 区间合并来解决。

问题一:为什么使用优先队列呢?

可以这样思考,魔法只能够作用于相同高度连续的一段竹子上。也就是说对于最高的那一段竹子无法被作用于剩余竹子的魔法所干预,因此优先处理最高的一段竹子一定是最优解。这也就是为什么需要使用优先队列,优先队列负责弹出高度最高的那一段区间。

问题二:如何处理区间呢?

魔法只能作用于相同高度且连续的一段竹子上,定义数据结构 myNode 代表一段相同高度且连续的区间,l,r 表示区间在的索引范围,val 表示这段竹子的高度。

既然优先队列弹出最高高度的区间段,很显然该区间段可能不止一个一段,可能是有多段。对于连续的区间段,可以将其拼接为一段区间,就如下图所示:

在这里插入图片描述

上图中,区间1与区间2因为区间段连续可以合并,而区间3因为不与区间2连续因此无法合并。

了能够将所有连续的区间段合并,这就要需要优先队列中,如果区间最高高度相同,则按照区间次序排序。这样就能保证能够合并优先队列中的区间。

代码逻辑

弹出队列中所有最高竹子的区间段,将能够合并的区间段进行合并,最后判断合并后剩余多少个区间段即是需要使用魔法的次数。随后将处理后的高度 c_value 修改剩余区间段的最高高度 value 并将其重新压入队列中。

🌷 代码编写

#include <iostream>
#include <string.h>
#include <queue>
#include <math.h>
#define ll long long
using namespace std;
// 区间数据结构
class myNode
{
public :int l, r;ll val;myNode(int l, int r, ll val) {this->l = l;this->r = r;this->val = val;}
};
// 自定义排序方式
bool operator < (const myNode& t,const myNode& node) {if (t.val == node.val) {return t.l > node.l;}return t.val < node.val;
}
int main(){// 优先队列priority_queue<myNode>qu;int n;cin >> n;vector<ll>nums;for (int i = 0; i < n; ++i) {ll val;cin >> val;nums.push_back(val);}// 存入区间的初始化状态for (int i = 0; i < n; ++i) {int l, r;l = r = i;while (r + 1 < n && nums[r + 1] == nums[l]) {++r;}i = r;if (nums[l] != 1) {qu.push(myNode(l, r, nums[l]));}}int t = 0;while (!qu.empty()) {myNode top = qu.top();int left = top.l;int right = top.r;ll value = top.val;// 被魔法处理后的区间高度ll c_value = sqrt(value / 2 + 1);qu.pop();// 合并区间while (!qu.empty() && qu.top().val == value) {myNode tmp = qu.top();int l = tmp.l;int r = tmp.r;qu.pop();// 能够区间合并if (l == right + 1) {right = r;}// 无法区间合并else {if (c_value != 1) {qu.push({ left,right,c_value });}left = l;right = r;++t;}}if (c_value != 1) {qu.push({ left,right,c_value });}++t;}cout << t;return 0;
}

💗写在最后

总的来说,这次C++ B组的题目考察的知识点没有过分的难,基本上是常考的例如搜索、BFS、DFS、前缀和、二分等等,因此我相信只需要充分准备在赛场上还是能够得心应手。

最后感谢你的观看💗

image-20200916114846002

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_88874.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

92年程序员发帖晒薪资称自己很迷茫,网友:老弟你可以了

当下&#xff0c;是一个“向钱看&#xff0c;向厚赚”的社会。快节奏的生活下&#xff0c;家庭、工作各方面压力很容易使年轻人陷入迷茫和焦虑。 与其他行业相比&#xff0c;程序员的高薪让人羡慕。那么&#xff0c;对于那些真正达到这么多收入的人来说&#xff0c;他们是怎么…

Mysql安装包安装教程(亲测简单高效版)

Mysql安装包安装教程&#xff08;亲测简单高效版&#xff09;安装流程mysql安装SQLyog安装安装流程 mysql安装 1.下载mysql&#xff0c;官方地址&#xff1a;mysql官网 2.解压mysql安装包到任意目录下 3.新建my.ini文件 4.配置my.ini [mysqld] basedirD:\Program Files\J…

sql语法:详解DDL

Mysql版本&#xff1a;8.0.26 可视化客户端&#xff1a;sql yog 目录一、DDL是什么&#xff1f;二、和数据库相关的DDL2.1 创建数据库2.2 删除数据库2.3 查看所有的数据库&#xff0c;当前用户登录后&#xff0c;可以看到哪些数据库2.4 查看某个数据库的详细定义2.5 修改数据库…

Windows系统GIT安装与GitHub远程仓库

文章目录Windows系统GIT安装Git是什么windows环境安装环境变量验证安装GitHub与远程仓库GitHub是什么GitHub账号注册创建本地SSH KeyGitHub接入本地电脑公匙创建个人远程库传送门Windows系统GIT安装 Git是什么 Git&#xff08;读音为/gɪt/&#xff09;是一个开源的分布式版本…

接口测试之Postman使用全指南(原来使用 Postman测试API接口如此简单)

目录 一、Postman背景介绍 二、Postman的操作环境 三、Postman重要提示&#xff1a; 四、什么是接口测试 五、接口测试工具 六、接口测试流程 七、接口测试执行 八、全局变量和环境变量 九、postman接口关联 十、postman动态参数 十一、postman断言 十二、postman用…

Unity --- Transform类

1.一个很有意思的事实是Transform类不仅用来管理游戏物体的位置缩放旋转&#xff0c;还用来管理游戏物体的父物体与子物体之间的关系 当游戏物体A的trasnform类a是游戏物体B的transform类b的父类的话&#xff0c;游戏物体A就是游戏物体B的父物体 2.如何访问脚本当前挂载的游戏…

手把手教你安装VSCode(附带图解步骤)

一、前端工具vscode 1.1、概述 前端开发是创建Web页面或app等前端界面呈现给用户的过程&#xff0c;通过HTML&#xff0c;CSS及JavaScript以及衍生出来的各种技术、框架、解决方案&#xff0c;来实现互联网产品的用户界面交互 [1] 。它从网页制作演变而来&#xff0c;名称上有…

如何用Python对股票数据进行LSTM神经网络和XGboost机器学习预测分析(附源码和详细步骤),学会的小伙伴们说不定就成为炒股专家一夜暴富了

前言 最近调研了一下我做的项目受欢迎程度&#xff0c;大数据分析方向竟然排第一&#xff0c;尤其是这两年受疫情影响&#xff0c;大家都非常担心自家公司裁员或倒闭&#xff0c;都想着有没有其他副业搞搞或者炒炒股、投资点理财产品&#xff0c;未雨绸缪&#xff0c;所以不少…

你单位数字化转型了吗?

写在前面&#xff1a;本文由Bing AI和我一起完成&#xff0c;它完成90%内容&#xff0c;致谢&#xff01; 1.数字化转型 近两年数字化转型在社会面搞得轰轰烈烈&#xff0c;数字化转型是指&#xff0c;利用新一代信息技术&#xff0c;构建数据的采集、传输、存储、处理和反馈的…

抓取某话题下指定时间内的微博数据,包括博文数据、评论信息等(可通过高级搜索筛选时间)

代码有点长&#xff0c;完整代码放在文章最后了。 最后的数据存储为了3个表&#xff0c;表的各字段如下&#xff1a; # csv头部 writer.writerow((话题链接, 话题内容, 楼主ID, 楼主昵称, 楼主性别, 发布日期,发布时间, 转发量, 评论量, 点赞量, 评论者ID, 评论者昵称,评论者…

低代码开发公司:用科技强力开启产业分工新时代!

实现办公自动化&#xff0c;是不少企业的共同追求。低代码开发公司会遵循时代发展规律&#xff0c;注入强劲的科技新生力量&#xff0c;在低代码开发市场厚积爆发、努力奋斗&#xff0c;推动企业数字化转型升级&#xff0c;为每一个企业的办公自动化升级创新贡献应有的力量。 一…

Matlab仿真,数字基带传输系统的设计实验报告

实验目的 1、提高独立学习的能力&#xff1b; 2、培养发现问题、解决问题和分析问题的能力&#xff1b; 3、学习Matlab 的使用&#xff1b; 4、掌握基带数字传输系统的仿真方法&#xff1b; 5、熟悉基带传输系统的基本结构&#xff1b; 6、理解奈奎斯特第一准则&#xff1b; 7…

echarts入门基础教程

目录 效果图 1.下载资源 新建项目 2.引入echarts 3.准备一个呈现图表的盒子 4.初始化echarts实例对象 5.准备配置项 6.将配置项设置给echarts实例对象 7.完整代码 效果图 1.下载资源 新建项目 去官网下载echarts压缩包&#xff0c;在包里的dist文件里找到echarts.min.j…

sql语法:事务的”那些事“

Mysql版本&#xff1a;8.0.26 可视化客户端&#xff1a;sql yog 目录前言一、事务是什么&#xff1f;二、事务的特点三、如何提交事务和回滚事务?3.1 手动提交3.2 自动提交模式下开启事务3.3 注意事项四、事务的隔离级别4.1 模拟事务安全问题4.1.1 脏读问题模拟如下&#xff1…

【模块介绍】6×6矩阵键盘(硬件部分和扫描方式)

目录 概述 原理图 扫描方式 扫描法 单个按键按下 多个按键按下 行反转法 图解 成品 概述 矩阵键盘非常常见 就是利用键盘组成矩阵来减少IO口的使用 做成66的矩阵键盘可以使用12个IO口读取36个按键 矩阵键盘的优势在于成本低&#xff0c;无需其他芯片即可实现功能 …

Android WMS工作原理浅析(一)

WMS(WindowManagerService)相关概念 window:它是一个抽象类&#xff0c;具体实现类为 PhoneWindow &#xff0c;它对 View 进行管理。Window是View的容器&#xff0c;View是Window的具体表现内容&#xff1b; windowManager:是一个接口类&#xff0c;继承自接口 ViewManager &…

Mac上初次使用vite新建Vue3项目需要注意,自己的错误记录

执行npm init vitejs/app时 报错&#xff1a; internal/modules/cjs/loader.js:1089 throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath); 一开始网上找原因&#xff0c;以为是node的版本过低&#xff0c;但是看了是自己的弄的版本号是12.1.x.x刚刚好跨过门槛…

ElasticSearch学习(十一)—— es7.2升级log4j版本

下载log4j2.17 下载地址&#xff1a;Apache Logging Serviceshttps://logging.apache.org/ 查找es安装目录下需要替换的log4j文件 /opt/elk# find . -name log4j* ./elasticsearch-7.2.0/lib/log4j-api-2.11.1.jar ./elasticsearch-7.2.0/lib/log4j-core-2.11.1.jar ./elastics…

【VulnHub靶场】——BEELZEBUB: 1

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门创作初心&#xff1a;对于计算机的学习者来说&#xff0c;初期的学习无疑是最迷茫和难以坚持的&#xff0c;中后期主要是经验和能力的提高&#xff0c;我也刚接触计算机1年&#xff0c;也在不断的探索&#xf…

在Windows系统上安装MacOS虚拟机

在Windows上安装MacOS虚拟机&#xff0c;准备工作主要分三个方面&#xff1a;电脑配置、MacOS镜像和虚拟机软件。 目录 电脑配置&#xff1a;开启虚拟化 MacOS镜像&#xff1a;下载一个MacOS镜像 虚拟机安装&#xff1a;安装WMware虚拟机 创建虚拟机&#xff1a;创建MacOS…