0


2022 蓝桥杯省赛 C++ B组 解题代码

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

前言: 本题解不保证代码正确, 主要提供一种博主在比赛过程中的做题思路, 仅供参考. 如果您对本文有什么看法, 欢迎大佬们评论区交流.

本文首发于 2022.4.9


试题 A: 九进制转十进制

解题思路

我们可以通过进制转换的通俗写法, 把

    (
   
   
    2022
   
   
    
     )
    
    
     9
    
   
  
  
   (2022)_9
  
 
(2022)9​ 转化成10进制.

或者, 直接计算

    2
   
   
    ∗
   
   
    
     9
    
    
     0
    
   
   
    +
   
   
    2
   
   
    ∗
   
   
    
     9
    
    
     1
    
   
   
    +
   
   
    0
   
   
    ∗
   
   
    
     9
    
    
     2
    
   
   
    +
   
   
    2
   
   
    ∗
   
   
    
     9
    
    
     3
    
   
  
  
   2 * 9^0 + 2 * 9^1 + 0 * 9^2 + 2 * 9^3
  
 
2∗90+2∗91+0∗92+2∗93 填空.

参考答案:

1478

参考代码

#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;intmain(){int x =2022;int res =0, base =1;do{
        res += x %10* base;
        x /=10;
        base *=9;}while(x);printf("%d\n", res);/** 或者直接计算
    int qaq = 2 + 2 * 9 + 2 * 9 * 9 * 9;
    cout << qaq << endl;
    */return0;}

试题 B: 顺子日期

解题思路

直接暴力枚举所有情况即可.

我的做法可能稍微有些复杂, 思路就是转化成

yyyymmdd

的字符串, 然后判断是否存在顺子.

此处可以动脑, 例如字符串不用以"1"的形式存储, 年份"2022"无意义 等等等…

参考答案:

14

参考代码

#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;int month[]={0,31,28,31,30,31,30,31,31,30,31,30,31};

string fact(int x){
    string s;do{
        s +='0'+ x %10;
        x /=10;}while(x);reverse(s.begin(), s.end());if(s.size()==1) s ="0"+ s;return s;}booljudge(const string& s){int len =0, last =-2;for(int i =0; i < s.size();++i){int op = s[i]-'0';
        op == last +1?++len : len =1;

        last = op;if(len ==3)return1;}return0;}intmain(){int res =0;rep(m,12){rep(d, month[m]){
            string s ="2022"+fact(m)+fact(d);if(judge(s)) res++,printf("%s\n", s.c_str());}}

    cout << res << endl;return0;}

试题 C: 刷题统计

解题思路

我们很容易发现这个题考查了周期性.

每周做的题目数量:

 week = 5a + 2b

, 因此我们可以通过

    n
   
   
    u
   
   
    m
   
   
    =
   
   
    ⌊
   
   
    
     n
    
    
     
      w
     
     
      e
     
     
      e
     
     
      k
     
    
   
   
    ⌋
   
  
  
   num = \lfloor \frac{n}{week} \rfloor
  
 
num=⌊weekn​⌋来计算出答案包含

 
  
   
    n
   
   
    u
   
   
    m
   
  
  
   num
  
 
num个**整周**.然后再暴力最后一周的情况即可.

特别需要注意的是, 本题需要用

long long

,

ll

最大值可以大概认为是

     9
    
    
     ∗
    
    
     1
    
    
     
      0
     
     
      18
     
    
   
   
    9 * 10^{18}
   
  
 9∗1018, 因此
 
  
   
    
     w
    
    
     e
    
    
     e
    
    
     k
    
   
   
    week
   
  
 week并不会导致溢出.

参考代码

#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;intmain(){
    ll a, b, n; cin >> a >> b >> n;

    ll week =5* a +2* b;

    ll res = n / week *7;
    n %= week;rep(i,5)if(n >0) n -= a, res++;rep(i,2)if(n >0) n -= b, res++;

    cout << res << endl;return0;}

试题 D: 修剪灌木

解题思路

这题读完之后, 很容易能感觉到最优解是存在一个最小循环节的.

通常这种题我们保险起见可以进行如下模拟:

左 -> 右 -> 左 -> 右

这样理论上一定已经包含一个最小循环节了.

但是上述写法比较复杂, 我写完后发现了规律. 我们发现答案是具有对称性的.

例如

n = 6

答案:

10 8 6 6 8 10

, 我们结合

n = 3

答案

4 2 4

. 经过冷静分析(一顿瞎蒙)后, 我们发现这是个等差数列, 我们按照规律模拟输出即可.

特别的, 要注意模拟方式是否需要特判

n == 1

的情况, 此时答案为:

1

.

不会有一群人都挂在这上了吧, 不会吧不会吧???

参考代码

#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;constint N =1E5+10;int a[N];intmain(){int n; cin >> n;if(n ==1){printf("%d\n",1);return0;}rep(i,(n +1)/2){int l = i, r = n - i +1;
        a[l]= a[r]=(n - i)*2;}rep(i, n)printf("%d\n", a[i]);return0;}

试题 E: X 进制减法

解题思路

这题… 我拿到之后看了5分钟, 愣是不敢写. 别问, 问就是没读懂.

又是经过一顿冷静分析xia bi cai之后, 明白了.

解释样例: (以下数字为10进制下的含义)

假设把

     321
    
   
   
    321
   
  
 321放入数组
 
  
   
    
     a
    
    
     [
    
    
     ]
    
   
   
    a[]
   
  
 a[], 有 
 
  
   
    
     a
    
    
     [
    
    
     0
    
    
     ]
    
    
     =
    
    
     3
    
    
     ,
    
    
     a
    
    
     [
    
    
     1
    
    
     ]
    
    
     =
    
    
     2
    
    
     ,
    
    
     a
    
    
     [
    
    
     2
    
    
     ]
    
    
     =
    
    
     1
    
   
   
    a[0] = 3, a[1] = 2, a[2] = 1
   
  
 a[0]=3,a[1]=2,a[2]=1 .

现在

     a
    
    
     [
    
    
     1
    
    
     ]
    
    
     =
    
    
     2
    
   
   
    a[1] = 2
   
  
 a[1]=2这一位为
 
  
   
    
     10
    
   
   
    10
   
  
 10进制, 其高位有
 
  
   
    
     a
    
    
     [
    
    
     0
    
    
     ]
    
    
     =
    
    
     3
    
   
   
    a[0] = 3
   
  
 a[0]=3, 说明如果不产生进位, 那么有
 
  
   
    
     a
    
    
     [
    
    
     1
    
    
     ]
    
    
     =
    
    
     3
    
    
     ∗
    
    
     10
    
    
     +
    
    
     2
    
    
     =
    
    
     32
    
   
   
    a[1] = 3 * 10 + 2 = 32
   
  
 a[1]=3∗10+2=32.

同理: 对于

     a
    
    
     [
    
    
     2
    
    
     ]
    
    
     =
    
    
     1
    
   
   
    a[2] = 1
   
  
 a[2]=1, 其高位
 
  
   
    
     a
    
    
     [
    
    
     1
    
    
     ]
    
    
     =
    
    
     32
    
   
   
    a[1] = 32
   
  
 a[1]=32(我们刚计算的), 如果不进位, 则
 
  
   
    
     a
    
    
     [
    
    
     0
    
    
     ]
    
    
     =
    
    
     32
    
    
     ∗
    
    
     2
    
    
     +
    
    
     1
    
    
     =
    
    
     65
    
   
   
    a[0] = 32 * 2 + 1 = 65
   
  
 a[0]=32∗2+1=65

能get到吗?

我们观察题目, 题目规定了

    A
   
   
    ≥
   
   
    B
   
  
  
   A \ge B
  
 
A≥B, 且问最小的

 
  
   
    A
   
   
    −
   
   
    B
   
  
  
   A - B
  
 
A−B.

那么答案一定是所有位的进制越小越好.

    A
   
   
    ,
   
   
    B
   
  
  
   A, B
  
 
A,B两个数**右对齐**后, **从右往左**数第

 
  
   
    i
   
  
  
   i
  
 
i位的最小进制为: 

 
  
   
    m
   
   
    a
   
   
    x
   
   
    (
   
   
    {
   
   
    A
   
   
    o
   
   
    
     p
    
    
     i
    
   
   
    +
   
   
    1
   
   
    ,
   
   
    B
   
   
    o
   
   
    
     p
    
    
     i
    
   
   
    +
   
   
    1
   
   
    ,
   
   
    2
   
   
    }
   
   
    )
   
  
  
   max(\{ Aop_i + 1, Bop_i + 1, 2 \})
  
 
max({Aopi​+1,Bopi​+1,2}), 其中

 
  
   
    A
   
   
    o
   
   
    
     p
    
    
     i
    
   
   
    ,
   
   
    B
   
   
    o
   
   
    
     p
    
    
     i
    
   
  
  
   Aop_i, Bop_i
  
 
Aopi​,Bopi​为这位上, 

 
  
   
    A
   
   
    ,
   
   
    B
   
  
  
   A, B
  
 
A,B的数字.

右对齐解释:

例如: A = { 3, 2, 1 }, B = { 4, 0 }

则对齐后如下:

A:321
B:40
M:452// 最小进制位 

本例参考答案:

27

所以n是真的有用的吗?

参考代码

#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;constint N =1E5+10, mod =1000000007;int a[N], b[N];intmain(){int n; cin >> n;int la; cin >> la;rep(i, la)scanf("%d",&a[i]);int lb; cin >> lb;rep(i, lb)scanf("%d",&b[i]);int suma =0, sumb =0;rep(i, la - lb){// 之前的和是suma, 这位进制至少是max(ai + 1, 2)int low =max(a[i]+1,2);
        suma =(1ll* suma * low + a[i])% mod;}int py = la - lb;//偏移量rep(i, lb){int opa = a[i + py], opb = b[i];int low =max(opa, opb)+1;
        low =max(2, low);

        suma =(1ll* suma * low + opa)% mod;
        sumb =(1ll* sumb * low + opb)% mod;}int res =(suma - sumb + mod)% mod;
    cout << res << endl;return0;}

试题 F: 统计子矩阵

解题思路

赛场思路

这题读完, 立马想到二位前缀和不过分吧? 然后立马得到了一个

    O
   
   
    (
   
   
    
     n
    
    
     4
    
   
   
    )
   
  
  
   O(n^4)
  
 
O(n4)的做法也不过分吧?

然后我们发现复杂度爆炸, 因此考虑优化:

我们发现, 如果假设子矩阵左上角

    (
   
   
    
     x
    
    
     1
    
   
   
    ,
   
   
    
     y
    
    
     1
    
   
   
    )
   
  
  
   (x_1, y_1)
  
 
(x1​,y1​), 右下角

 
  
   
    (
   
   
    
     x
    
    
     2
    
   
   
    ,
   
   
    
     y
    
    
     2
    
   
   
    )
   
  
  
   (x_2, y_2)
  
 
(x2​,y2​), 那么我们可以枚举

 
  
   
    
     x
    
    
     1
    
   
   
    ,
   
   
    
     y
    
    
     1
    
   
   
    ,
   
   
    
     x
    
    
     2
    
   
  
  
   x_1, y_1, x_2
  
 
x1​,y1​,x2​, 然后

 
  
   
    
     y
    
    
     2
    
   
  
  
   y_2
  
 
y2​是具有单调性的, 因此优化后复杂度为: 

 
  
   
    O
   
   
    (
   
   
    
     n
    
    
     3
    
   
   
    l
   
   
    o
   
   
    g
   
   
    n
   
   
    )
   
  
  
   O(n^3logn)
  
 
O(n3logn). 虽然有些爆炸, 但是骗分足够了.
赛后被教育

这题可以通过枚举矩阵宽度来把一个二维问题, 转化成一个一维问题.

此时变成了问题所求便成了一个最大连续子段问题, 可以通过双指针来求解.

最终复杂度为:

    O
   
   
    (
   
   
    
     n
    
    
     3
    
   
   
    )
   
  
  
   O(n^3)
  
 
O(n3)

参考代码

/* 做法一: 复杂度O(n^3logn) */#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;constint N =5E2+10;int a[N][N], s[N][N];intcalc(int x1,int y1,int x2,int y2){return s[x2][y2]- s[x2][y1 -1]- s[x1 -1][y2]+ s[x1 -1][y1 -1];}intmain(){int n, m, k; cin >> n >> m >> k;rep(i, n)rep(j, m){scanf("%d",&a[i][j]);
            s[i][j]= s[i -1][j]+ s[i][j -1]- s[i -1][j -1]+ a[i][j];}

    ll res =0;rep(x1, n)rep(y1, m){for(int x2 = x1; x2 <= n;++x2){if(calc(x1, y1, x2, y1)> k)break;int l = y1, r = m;while(l < r){int mid = l + r +1>>1;if(calc(x1, y1, x2, mid)<= k) l = mid;else r = mid -1;}

                res +=(r - y1 +1);}}

    cout << res << endl;return0;}/* 做法2: 复杂度O(n^3) */#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;constint N =5E2+10;int a[N][N], s[N][N];intcalc(int cur,int L,int R){return s[cur][R]- s[cur][L -1];}intmain(){int n, m, k; cin >> n >> m >> k;rep(i, n)rep(j, m){scanf("%d",&a[i][j]);
        s[i][j]= s[i][j -1]+ a[i][j];}int res =0;rep(L, m)for(int R = L; R <= m;++R){int top =0, down =0;for(int l =1, r =0; l <= n;++l){while(r +1<= n and down +calc(r +1, L, R)- top <= k){
                down +=calc(++r, L, R);}if(down - top <= k){
                res += r - l +1;}

            top +=calc(l, L, R);}}

    cout << res << endl;return0;}

试题 G: 积木画

解题思路

这题… 我提供个我的做题思维吧…

我并不是很擅长递推类问题, 首先删除掉了L形积木, 我发现如果只有I型积木, 答案就是个斐波那契数列. 此时我大胆猜测, 如果加上了L积木, 答案应该是可以通过类似的方式递推出来的.

最终结论为:

a[i] = a[i - 1] + a[i - 2] + 2 * a[i - 3]

.

解释: 首先我们考虑I型只需要

     2
    
    
     ∗
    
    
     2
    
   
   
    2 * 2
   
  
 2∗2的格子即可完成不同种排列组合, **L型**则需要
 
  
   
    
     3
    
    
     ∗
    
    
     2
    
   
   
    3 * 2
   
  
 3∗2.

     i
    
   
   
    i
   
  
 i列时, 比
 
  
   
    
     i
    
    
     −
    
    
     1
    
   
   
    i - 1
   
  
 i−1列多出了一列, 这一列我们只能**竖着放I**.

而比

     i
    
    
     −
    
    
     2
    
   
   
    i - 2
   
  
 i−2列多出了两列, 这两列我们只能**横着放I**, 否则会与
 
  
   
    
     i
    
    
     −
    
    
     1
    
   
   
    i - 1
   
  
 i−1列的情况重复.

同理, 比

     i
    
    
     −
    
    
     3
    
   
   
    i - 3
   
  
 i−3列多出了三列, 此时我们只能放**L型**, 否则同样会和前面冲突, 而对于
 
  
   
    
     3
    
    
     ∗
    
    
     2
    
   
   
    3 * 2
   
  
 3∗2的格子, 有两种放**L型**积木的方式, 因此要
 
  
   
    
     ×
    
    
     2
    
   
   
    \times 2
   
  
 ×2.

对于多出更多列的方式, 我们发现均会与之前的情况重复.

参考代码

#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;constint N =1E7+10, mod =1000000007;int a[N];// 当然你可以选择不开数组.intmain(){int n; cin >> n;

    a[1]=1, a[2]=2, a[3]=5;for(int i =4; i <= n;++i){
        a[i]= a[i -1];// 加一列在后面
        a[i]=(a[i]+ a[i -2])% mod;
        a[i]=(0ll+ a[i]+ a[i -3]*2)% mod;}

    cout << a[n]<< endl;return0;}

试题 H: 扫雷

解题思路

这题… 其实如果按照我的方式理解题意… 我算是暴力了一下

最终复杂度为:

    O
   
   
    (
   
   
    n
   
   
    
     r
    
    
     2
    
   
   
    l
   
   
    o
   
   
    g
   
   
    n
   
   
    )
   
  
  
   O(nr^2logn)
  
 
O(nr2logn)

其实可以通过离散化的方式删除掉后面的

    l
   
   
    o
   
   
    g
   
  
  
   log
  
 
log但是写起来太复杂了.

代码供大家参考着玩吧, 比赛时乱YY的, 不一定对.

参考代码

#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;typedef pair<int,int> PII;structnode{int x, y, r;node(int a,int b,int c){
        x = a, y = b, r = c;}};

map<PII, PII> mp;// { x, y } { r, num }doublegetdis(const pair<int,int>& a,const pair<int,int>& b){int x1 = a.first, y1 = a.second;int x2 = b.first, y2 = b.second;
    ll x =(x1 - x2), y =(y1 - y2);returnsqrt(x * x + y * y);}intmain(){int n, m; cin >> n >> m;rep(i, n){int x, y, r;scanf("%d %d %d",&x,&y,&r);

        PII p =make_pair(x, y);if(!mp.count(p)) mp[p]=make_pair(r,1);else{
            PII op = mp[p];
            mp[p]=make_pair(max(op.first, r), op.second +1);}}

    queue<node> q;rep(i, m){int x, y, r;scanf("%d %d %d",&x,&y,&r);
        q.push(node(x, y, r));int res =0;while(!q.empty()){
            node op = q.front(); q.pop();
            x = op.x, y = op.y, r = op.r;
            PII p =make_pair(x, y);for(int px = x - r; px <= x + r;++px){for(int py = y - r; py <= y + r;++py){
                    PII pp =make_pair(px, py);if(getdis(p, pp)> r)continue;if(!mp.count(pp))continue;
                    PII now = mp[pp];
                    mp.erase(pp);

                    q.push(node(pp.first, pp.second, now.first));
                    res += now.second;}}}printf("%d\n", res);}return0;}

试题 I: 李白打酒加强版

解题思路

这题, 读完了之后冷静分析, 发现是个dp, 然后我直接写了暴力…

但是最后做完了题, 没事干, 于是尝试着写了写… 没想到过了样例, 然后开始对拍… 没想到过了对拍…

好吧好吧, 删掉暴力, 自信交!

对于本题的讲解, 其实博主并不擅长dp, 这道题只是尝试着用我认为的dp分析方式写出来了, 就不在这里乱叨叨了.

参考代码

/* 暴力做法, 40%没问题 */#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;constint mod =1000000007;intmain(){int n, m; cin >> n >> m;constint B = n + m;constint TOP =1<< B;int res =0;for(int i =0; i < TOP;++i){if(i %2==1orbitset<32>(i).count()!= n)continue;

        ll now =2;for(int j = B -1; j >=0;--j){if(i >> j &1) now *=2;else now--;}if(!now) res++;}

    cout << res << endl;return0;}/* DP 100% O(n^3) 代码注释为比赛时的注释, 未修改 */#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;constint N =1E2+10, mod =1000000007;int dp[N *2][N][N];// 前i步中, 还有k个花 有j口酒的情况int n, m;intfact(){int all = n + m;

    dp[0][2][m]=1;rep(i, all){// 枚举总步数for(int j =0; j <= m;++j){// 枚举当前步数下, 还有多少口酒for(int k = j; k <= m;++k){// 枚举后续还能遇到多少个花
                dp[i][j][k]=0;

                dp[i][j][k]= dp[i -1][j +1][k +1];if(j %2==0) dp[i][j][k]=(dp[i][j][k]+ dp[i -1][j /2][k])% mod;}}}return dp[all -1][1][1];}intmain(){
    cin >> n >> m;int res =fact();
    cout << res << endl;return0;}

试题 J: 砍竹子

解题思路(本题思路纯属扯淡)

这题, 读完了好眼熟啊… 感觉某位前辈好像在冥冥之中给过我指引, 但是我好像写的并不太对…

本来尝试讲解了一下自己的思路, 发现狗屁不通!!!

所以本题看着玩吧 (摆烂.png)

参考代码

#include<bits/stdc++.h>#definerep(i, n)for(int i =1; i <=(n);++i)usingnamespace std;typedeflonglong ll;constint N =2E5+10;
ll a[N];structnode{
    ll val;int l, r;node(ll a,int b,int c){
        val = a;
        l = b, r = c;}booloperator<(const node& t)const{return val < t.val;}};

ll fact(ll x){
    x = x /2;returnsqrt(x +1);}intmain(){int n; cin >> n;rep(i, n)scanf("%lld",&a[i]);

    set<ll> st[2];

    ll res =0;rep(i, n){int id = i &1, anid = id ^1;
        st[id].clear();while(a[i]!=1){
            st[id].insert(a[i]);if(!st[anid].count(a[i])) res++;

            a[i]=fact(a[i]);}}

    cout << res << endl;return0;}

退役前: DP不会, 规律找不到.

退役后: 卧槽, 我能写出来DP了? 卧槽, 我还会递推找规律了?

END

标签: 算法 c++ 蓝桥杯

本文转载自: https://blog.csdn.net/weixin_45799835/article/details/124066566
版权归原作者 逍遥Fau 所有, 如有侵权,请联系我们删除。

“2022 蓝桥杯省赛 C++ B组 解题代码”的评论:

还没有评论