2022年十三届蓝桥杯B组题解(全)

十三届蓝桥杯省赛C++B组 真题题解

  • A.九进制转十进制
  • B.顺子日期
  • C.刷题统计
          • 题目描述
          • 输出格式
          • 输入样例
          • 输出样例
  • D.修剪灌木
          • 题目描述
          • 输入格式
          • 输出格式
          • 输入样例
          • 输出样例
  • E.X进制减法
          • 题目描述
          • 输入格式
          • 输出格式
          • 输入样例
          • 输出样例
          • 数据范围与提示
  • F.统计子矩阵题目描述
          • 输出格式
          • 输入样例
          • 输出样例
          • 数据范围与提示
  • G.积木画
          • 题目描述
          • 输入格式
          • 输出格式
          • 输入样例
          • 输出样例
  • H.扫雷
          • 题目描述
          • 输入格式
          • 输出格式
          • 输入样例
          • 输出样例
          • 数据范围与提示
  • I.李白打酒加强版
          • 题目描述
          • 输入格式
          • 输出格式
          • 输入样例
          • 输出样例
          • 数据范围与提示
  • J.砍竹子
          • 题目描述
          • 输出格式
          • 输入样例
          • 输出样例
          • 数据范围与提示

A.九进制转十进制

九进制正整数(2022)装换成十进制等于多少?
2022年十三届蓝桥杯B组题解(全)_第1张图片

B.顺子日期

2022年十三届蓝桥杯B组题解(全)_第2张图片

枚举

14

C.刷题统计

题目描述

小明决定从下周一开始努力刷题准备蓝桥杯竞赛。
他计划周一至周五每天做 a 道题目,周六和周日每天做 b 道题目。
请你帮小明计算,按照计划他将在第几天实现做题数大于等于 n 题?
输入格式
输入一行包含三个整数a, b 和n.
50% 的评测用例:1 ≤ a, b, n ≤ 10^6;
100% 的评测用例:1 ≤ a, b, n ≤ 10^18。

输出格式

输出一个整数代表天数。

输入样例

10 20 99

输出样例

8

#include
using namespace std;
int main()
{
    long long a,b,n,m;
    cin>>a>>b>>n;
    long long weekend=5*a+2*b,week=5*a;
    if(n%weekend>week)m=(n/weekend)*7+5+(n%weekend-5*a)/b+((n%weekend-5*a)%b>0);
    else m=(n/weekend)*7+(n%weekend/a)+(n%weekend>0);
    cout<<m<<endl;
    return 0;
}

D.修剪灌木

题目描述

爱丽丝要完成一项修剪灌木的工作。
有 N 棵灌木整齐的从左到右排成一排。
爱丽丝在每天傍晚会修剪一棵灌木,让灌木的高度变为 0 厘米。
爱丽丝修剪灌木的顺序是从最左侧的灌木开始,每天向右修剪一棵灌木。
当修剪了最右侧的灌木后,她会调转方向,下一天开始向左修剪灌木。
直到修剪了最左的灌木后再次调转方向。然后如此循环往复。
灌木每天从早上到傍晚会长高 1 厘米,而其余时间不会长高。
在第一天的早晨,所有灌木的高度都是 0 厘米。爱丽丝想知道每棵灌木最高长到多高。

输入格式

一个正整数N ,含义如题面所述。
30%的测试数据:1 100%的测试数据:1

输出格式

输出 N 行,每行一个整数,第 i 行表示从左到右第 i 棵树最高能长到多高。

输入样例

3

输出样例

4
2
4

画图打表 找规律 发现与树的坐标和最右的坐标的距离有关,且具有对称性

#include
using namespace std;
const int N=10010;
int a[N];
int main()
{
    int n;cin>>n;
    for(int i=1;i<=(n+1)/2;i++)
    a[i]=2*(n-i);
 
    for(int i=1;i<=(n+1)/2;i++)cout<<a[i]<<endl;
    if(n%2)//奇数
    {
        for(int i=(n+1)/2-1;i>0;i--)cout<<a[i]<<endl;
    }else for(int i=(n+1)/2;i>0;i--)cout<<a[i]<<endl;
    return 0;
}

E.X进制减法

题目描述

进制规定了数字在数位上逢几进一。
X 进制是一种很神奇的进制,因为其每一数位的进制并不固定!
例如说某种X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制:
则 X 进制数321 转换为十进制数为65。65=3*(210)+2(2)+1*(1)。
现在有两个 X 进制表示的整数 A 和 B,但是其具体每一数位的进制还不确定。
只知道 A 和 B 是同一进制规则,且每一数位最高为 N 进制,最低为二进制。
请你算出 A − B 的结果最小可能是多少。
请注意,你需要保证 A 和 B 在 X 进制下都是合法的,即每一数位上的数字要小于其进制。

输入格式

第一行一个正整数 N,含义如题面所述。
第二行一个正整数 Ma,表示 X 进制数 A 的位数。
第三行 Ma 个用空格分开的整数,表示 X 进制数 A 按从高位到低位顺序各个数位上的数字在十进制下的表示。
第四行一个正整数 Mb,表示 X 进制数 B 的位数。
第五行 Mb 个用空格分开的整数,表示 X 进制数 B 按从高位到低位顺序各个数位上的数字在十进制下的表示。
请注意,输入中的所有数字都是十进制的。
30%的测试数据:2≤N≤10,1≤Ma,Mb≤8。
100%的测试数据:2≤N≤1000,1≤Ma,Mb≤100000,B≤A。

输出格式

输出一行一个整数,表示X 进制数A − B 的结果的最小可能值转换为十进制后再模1000000007 的结果。

输入样例

11
3
10 4 0
3
1 2 0

输出样例

94

数据范围与提示

当进制为:最低位 2 进制,第二数位 5 进制,第三数位 11 进制时,减法得到的差最小。
此时A 在十进制下是108,B 在十进制下是 14,差值是 94。

#include
using namespace std;
typedef long long ll;
const int N=1e5+10,mod=1e9+7;
int a[N],b[N];
int main()
{
    int n,la,lb;
    cin>>n;
    cin>>la;
    for(int i=la;i>0;i--)cin>>a[i];
    cin>>lb;
    for(int i=lb;i>0;i--)cin>>b[i];
    ll t=1;
    ll sum=0;
    for(int i=1;i<=la;i++)
    {
        int m=max(a[i],b[i])+1;//取最小的合法进制
        if(m<2)m=2;
        sum+=(a[i]*t-b[i]*t);
        sum%=mod;
        t=t*m%mod;
 
    }
    cout<<sum<<endl;
    return 0;
 
}

F.统计子矩阵题目描述

给定一个 N × M 的矩阵A,请你统计有多少个子矩阵(最小 1 × 1,最大 N × M) 满足:
子矩阵中所有数的和不超过给定的整数K?
输入格式
第一行包含三个整数N, M 和K.
之后 N 行每行包含 M 个整数,代表矩阵A.
30%的测试数据:1≤N,M≤20;
70%的测试数据:1≤N,M≤100;
100%的测试数据:1≤N,M≤500;0≤Aij≤1000;1≤K≤250000000。

输出格式

一个整数代表答案。

输入样例

3 4 10
1 2 3 4
5 6 7 8
9 10 11 12

输出样例

19

数据范围与提示

满足条件的子矩阵一共有19,包含:
大小为1 × 1 的有10 个。
大小为1 × 2 的有3 个。
大小为1 × 3 的有2 个。
大小为1 × 4 的有1 个。
大小为2 × 1 的有3 个。

用双指针枚举矩阵中的左右边界,在每一个左右边界中,从上到下求子段中和小于等于k得数量

#include
using namespace std;
typedef long long ll;
const int N=510;
int a[N][N],b[N][N];
int main()
{
    int n,m,k;
    cin>>n>>m>>k;
    ll sum,cnt=0;
    for(int i=0;i<n;i++)
      for(int j=0;j<m;j++)
      {
          cin>>a[i][j];
 
      }
      for(int i=0;i<n;i++)
      for(int j=0;j<m;j++)
      {
 
          a[i][j]+=a[i][j-1];
      }
    for(int i=0;i<m;i++)//左边界
        for(int j=i;j<m;j++)//右边界
    {
        sum=0;
        for(int l=0,r=0;r<n;r++)//这里的lr其实是上下
        {
            sum+=a[r][j]-a[r][i-1];//前缀和
 
            while(sum>k&&l<=r){//不满足sum<=k时,去除上边界以上的面积(和)
                sum-=a[l][j]-a[l][i-1];
                l++;//上边界往下移
            }
            if(sum<=k&&l<=r)cnt+=r-l+1;
        }
 
    }
      cout<<cnt<<endl;
      return 0;
}
 
 

G.积木画

题目描述

小明最近迷上了积木画,有这么两种类型的积木,分别为 I 型(大小为 2 个单位面积)和 L 型(大小为 3 个单位面积):

同时,小明有一块面积大小为 2 × N 的画布,画布由 2 × N 个 1 × 1 区域构成。
小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式?
积木可以任意旋转,且画布的方向固定。

输入格式

输入一个整数N,表示画布大小。
对于所有测试用例,1 ≤ N ≤ 10000000。

输出格式

输出一个整数表示答案。由于答案可能很大,所以输出其对 1000000007 取模后的值

输入样例

3

输出样例

5

状压dp

#include
using namespace std;
const int N=1e7+10,mod=1e9+7;
long long f[N][3];/*N表示已经进行到的横坐标,3表示上下两格都满了,
2表示下面满了上面没满,1表示上面满了下面没满,0则表示上下两格都是空的*/
int main()
{
    int n;
    cin>>n;
    f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        f[i][0]=(f[i-1][0]+f[i-1][1]+f[i-1][2])%mod;
        if(i>=2){
            f[i][0]=(f[i][0]+f[i-2][0])%mod;
            f[i][1]=(f[i-2][0]+f[i-1][2])%mod;
            f[i][2]=(f[i-2][0]+f[i-1][1])%mod;
        }
    }
    cout<<f[n][0]%mod;
    return 0;
}
 

H.扫雷

题目描述

小明最近迷上了一款名为《扫雷》的游戏。
其中有一个关卡的任务如下,在一个二维平面上放置着 n 个炸雷
第 i 个炸雷(xi, yi, ri) 表示在坐标(xi, yi) 处存在一个炸雷,它的爆炸范围是以半径为 ri 的一个圆。
为了顺利通过这片土地,需要玩家进行排雷。
玩家可以发射 m 个排雷火箭,小明已经规划好了每个排雷火箭的发射方向。
第 j 个排雷火箭(xj, yj, rj) 表示这个排雷火箭将会在(xj, yj) 处爆炸,它的爆炸范围是以半径为 rj 的一个圆,
在其爆炸范围内的炸雷会被引爆。同时,当炸雷被引爆时,在其爆炸范围内的炸雷也会被引爆。
现在小明想知道他这次共引爆了几颗炸雷?
你可以把炸雷和排雷火箭都视为平面上的一个点。
一个点处可以存在多个炸雷和排雷火箭。当炸雷位于爆炸范围的边界上时也会被引爆。

输入格式

输入的第一行包含两个整数n、m.
接下来的 n 行,每行三个整数xi, yi, ri,表示一个炸雷的信息。
再接下来的 m 行,每行三个整数xj, yj, rj,表示一个排雷火箭的信息。
40% 的评测用例:0 ≤ x, y ≤ 10^9; 0 ≤ n,m ≤ 10^3; 1 ≤ r ≤ 10:
100% 的评测用例:0 ≤ x, y ≤ 10^9; 0 ≤ n,m ≤ 5 × 10^4; 1 ≤ r ≤ 10:

输出格式

输出一个整数表示答案。

输入样例

2 1
2 2 4
4 4 2
0 0 5

输出样例

2

数据范围与提示

示例图如下,排雷火箭1 覆盖了炸雷1,所以炸雷1 被排除;炸雷1 又覆盖了炸雷2,所以炸雷2 也被排除。
2022年十三届蓝桥杯B组题解(全)_第3张图片

哈希

#include 
using namespace std;

const int N = 50010, M = 999997;//N:最多的点数 M用于哈希  在允许的范围内M大一点比较好
struct circle {
    int x, y, r;
}cir[N];
int id[M];//记录相同位置半径最大的雷在cir中的坐标
bool sc[M];//判断某位置的雷是否被引爆
#define ll long long
ll h[M];//储存哈希值
        //注:xxxxll(后面两个是LL 不是11 代表将xxxx转化为long long 类型)
ll get_key(int x, int y) {
    return x * 1000000001ll + y;//这样可以尽量使每个坐标拥有一个特定的哈希值
}
int find(int x, int y) {
    ll key = get_key(x, y);
    int t = (key%M + M) % M;//这样可以确保t为正值 并且控制哈希值的范围
    while (h[t] != -1 && h[t] != key) {//如果位置已经被占用则向后寻找
        if (++t == M)t = 0;//防止数组越界
    }
    return t;//相当于根据哈希值在h数组中找好了
}
int sqr(int x) {
    return x*x;
}
void dfs(int x, int y, int r) {
    sc[find(x, y)] = true;//先标记被引爆
    for (int i = x - r; i <= x + r; i++) {//枚举被引爆雷的周围
        for (int j = y - r; j <= y + r; j++) {
            if (sqr(i-x) + sqr(j-y) <= sqr(r)) {
                int t = find(i, j);
                if (id[t] && !sc[t]) {
                    dfs(i, j, cir[id[t]].r);
                }
            }
        }
    }
}
 
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof(h));//代表h[i]==-1代表h[i]位置没雷
    for (int i = 1; i <= n; i++) {
        int x, y, r;
        scanf("%d%d%d", &x, &y, &r);
        cir[i] = { x,y,r };
        int t = find(x, y);//寻找x y 的位置
        if (h[t] == -1)h[t] = get_key(x, y);//get_key:返回x y的哈希值
        if (!id[t] || cir[id[t]].r < r)id[t] = i;//更新id数组  这里id的值改变只改变了它的影响范围
 
 
    }
    while (m--) {
        int x, y, r;
        scanf("%d%d%d", &x, &y, &r);
        for (int i = x - r; i <= x + r; i++) {
            for (int j = y - r; j <= y + r; j++) {
                if (sqr(i-x) + sqr(j-y) <= sqr(r)) {//x^2+y^2<=r^2 枚举圆内所有点
                    int t = find(i, j);//找到点在哈希表中的位置
                    if (id[t] && !sc[t]) {//如果有雷且没有被引爆
                        dfs(i, j, cir[id[t]].r);//深搜 主要为了找到全部被引爆的雷 sc[i]==true代表i位置被引爆
                    }
 
                }
            }
        }
    }
    //遍历
    int res = 0;
    for (int i = 1; i <= n; i++) {
        if (sc[find(cir[i].x, cir[i].y)])res++;//枚举所有雷判断他所在的坐标是否引爆 这样即使多个雷在同一个坐标也能被计算
    }
    printf("%d\n", res);
    return 0;
}

I.李白打酒加强版

题目描述

话说大诗人李白,一生好饮。幸好他从不开车。
一天,他提着酒壶,从家里出来,酒壶中有酒 2 斗。他边走边唱:
无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。
这一路上,他一共遇到店 N 次,遇到花 M 次。
已知最后一次遇到的是花,他正好把酒喝光了。
请你计算李白这一路遇到店和花的顺序,有多少种不同的可能?
注意:壶里没酒( 0 斗) 时遇店是合法的,加倍后还是没酒;但是没酒时遇花是不合法的。

输入格式

输入包含多组测试数据。
第一行为T,表示存在T组测试数据,T不超过30。
对于每组测试数据,输入两个整数N 和M.
1 ≤ N, M ≤ 100。

输出格式

输出一个整数表示答案。由于答案可能很大,输出模1000000007 的结果。

输入样例

1
5 10

输出样例

14

数据范围与提示

如果我们用 0 代表遇到花,1 代表遇到店,14 种顺序如下:
010101101000000
010110010010000
011000110010000
100010110010000
011001000110000
100011000110000
100100010110000
010110100000100
011001001000100
100011001000100
100100011000100
011010000010100
100100100010100
101000001010100

动态规划dp
memset(&dp[0][0][0],0,sizeof(dp));//清空多维数组

#include 
using namespace std;
typedef long long ll;
const int N=220,mod=1000000007;
ll n,m,ans,len;
ll dp[N][N][N]; //前i次 有j次遇到店 剩下酒为k斗
int main()
{
    int t;
    cin>>t;
    while(t--){
 
    cin>>n>>m;      //N店  M花
    len=n+m;
    memset(&dp[0][0][0],0,sizeof(dp));//清空多维数组
    dp[0][0][2]=1; //初始有2斗酒
    for(int i=1;i<=len;i++)
    for(int j=0;j<=n;j++)
    for(int k=0;k<=100;k++)
    {
        dp[i][j][k]+=dp[i-1][j][k+1]; //遇到花
        if(j&&k%2==0) dp[i][j][k]+=dp[i-1][j-1][k/2]; //遇到店
        dp[i][j][k]%=mod;
    }
    dp[len][n][0]=dp[len-1][n][1];  //最后遇到的一个一定是花
    cout<<dp[len][n][0]<<endl;
    }
    return 0;
}

J.砍竹子

题目描述

这天,小明在砍竹子,他面前有 n 棵竹子排成一排,一开始第 i 棵竹子的高度为 hi.
他觉得一棵一棵砍太慢了,决定使用魔法来砍竹子。
魔法可以对连续的一段相同高度的竹子使用,假设这一段竹子的高度为H,
那么使用一次魔法可以把这一段竹子的高度都变为:

其中 ⌊x⌋ 表示对 x 向下取整。
小明想知道他最少使用多少次魔法可以让所有的竹子的高度都变为1。
输入格式
第一行为一个正整数 n,表示竹子的棵数。
第二行共 n 个空格分开的正整数 hi,表示每棵竹子的高度。
20%的测试数据:n ≤ 1000; hi ≤ 10^6。
100%的测试数据:n ≤ 2 × 10^5; hi ≤ 10^18。

输出格式

一个整数表示答案。

输入样例

6
2 1 4 2 6 7

输出样例

5

数据范围与提示

其中一种方案:
2 1 4 2 6 7
→ 2 1 4 2 6 2
→ 2 1 4 2 2 2
→ 2 1 1 2 2 2
→ 1 1 1 2 2 2
→ 1 1 1 1 1 1
共需要 5 步完成。

分析:先来说一个贪心策略,我们优先选择砍所剩竹子中高度最大的竹子,因为无论我们怎么砍高度低的竹子都不可能使得高度低的竹子高度变高,从而能够和高度高的竹子一块被砍,相反的,我们砍完高度高的竹子后由于高度变低,所以可能会跟原来高度低的竹子一块被砍,所以这种贪心策略显然是正确的。而且高度相同的连续竹子一定要放在一起砍,这是显然的,一次可以砍完的事情为什么非要分几次呢?有了这个策略我们再来看一下一棵竹子最多会被砍多少次,你可以先按竹子高度最高1e18来算,发现他经过6次就可以砍成高度为1的竹子,也就是说每棵竹子被砍的次数都不会超过6.

那我们可以开一个优先队列,里面存pair类型,第一维是竹子的高度,第二维是竹子的编号,那么我们先把所有的竹子放入优先队列,每次取出一棵竹子,并记录其编号,直到取到一棵竹子高度不等于前一棵竹子的高度或者编号与前一棵竹子编号不是相连的,这个时候就将砍的次数+1.注意由于高度是从高到低排的,我没有用结构体排序,所以pair两维都是按照从高到低的规则来进行排序的,所以先出队列的就是编号较大的,只需要进行一下判断高度是否相等以及编号是否连续即可。还有一点需要注意的就是,我们每次取出队头竹子,然后把这个竹子的高度和编号记录一下,用于判断后续竹子是否可以和当前竹子一块被砍,然后就可以直接把当前竹子砍掉并讲砍完后的高度连同其编号一同放入优先队列(前提是竹子被砍后高度不为1),直至队空为止。

最后分析一下复杂度:每棵竹子最多进6次队列,共有n棵竹子,由于队列中最多同时有n棵竹子,所以每次入队都是o(logn)的,所以说总的复杂度就是6nlogn,是可以通过所有数据的。

#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef pair<long long,int>PII;
priority_queue<PII>q;
int main()
{
	int n;
	cin>>n;
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		long long t;
		scanf("%lld",&t);
		if(t!=1)
		q.push({t,i});
	}
	while(!q.empty())
	{
		long long t=q.top().first;//记录当前竹子的高度 
		int r=q.top().second;//记录当前竹子的编号 
		q.pop();
		long long l=sqrt(t/2+1);
		if(l!=1)
		    q.push({l,r});
		while(!q.empty()&&q.top().first==t&&q.top().second==r-1)
		{
			r--;
			q.pop();//将队首竹子pop 
			if(l!=1)
			    q.push({l,r});//将砍完后的竹子放入优先队列 
		}
		ans++;//当发现竹子高度不一致或者编号不连续时记录一次砍的次数 
	}
	printf("%lld",ans);
	return 0;
}

也可以用结构体写堆(优先队列)

#include
using namespace std;
typedef long long ll;
struct node
{
    ll h;
    int num;
    bool operator<(const node &a)const
    {
        if(h==a.h)return num>a.num;
        return h<a.h;
    }
};
priority_queue<node>q;
int main()
{
    int n,ans=0;
    ll t;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&t);
        q.push((node){t,i});
    }
    while(q.top().h!=1)//最高的竹子高度为1那么肯定都砍完了结束
    {
        t=q.top().h;
        n=q.top().num-1;
        while(q.top().h==t&&q.top().num==n+1)
        {
            n++;
            q.pop();
            q.push((node){sqrt(t/2+1),n});
        }
        ans++;
    }
    cout<<ans<<endl;
    return 0;
     
}

用结构体写的优先队列 堆

struct node
{
    ll h;
    int num;
    bool operator<(const node &a)const
    {
        if(h==a.h)return num>a.num;
        return h<a.h;
    }
};
priority_queue<node>q;

  q.pop();
   q.push((node){sqrt(t/2+1),n});

用pair写的堆

typedef pair<long long,int>PII;
priority_queue<PII>q;
q.push({t,i});
q.pop();

你可能感兴趣的:(做题心得,蓝桥杯,算法,c++)