2022 第十三届蓝桥杯大赛软件赛省赛,C/C++ 大学B组题解

Posted 小哈里

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2022 第十三届蓝桥杯大赛软件赛省赛,C/C++ 大学B组题解相关的知识,希望对你有一定的参考价值。

2022 第十三届蓝桥杯大赛软件赛省赛,C/C++ 大学B组题解

文章目录

补题链接:地址

第1题 —— 九进制转十进制 (5分)

  • 进制转换,9的0次方乘2+9的1次方乘2+2次方乘0+三次方乘9,输出就行。
  • 答案:1478
#include <iostream>
using namespace std;
int main()
  cout<<2+2*9+2*9*9*9<<"\\n";
  return 0;

第2题 —— 顺子日期 (5分)

  • 年份确定了是2022,可以枚举出每一天,然后暴力判断一下就行。
  • 答案:14
#include<bits/stdc++.h>
using namespace std;
int days[13] =  0,31,28,31,30,31,30,31,31,30,31,30,31 ; //2022不是闰年
int main()
    int cnt = 0;
    for(int month = 1; month <= 12; month++)
    for (int day = 1; day <= days[month]; day++) 
        string s = "2022";
        if (month < 10) s += '0'+ to_string(month);
        else s += to_string(month);
        if (day < 10) s += '0' + to_string(day);
        else s += to_string(day);
        if(s.find("012") != s.npos || s.find("123") != s.npos)
            cnt++;
        
    
    cout<<cnt<<"\\n";
    return 0;

第3题 —— 刷题统计 (10分)

  • 不难想到暴力模拟,day++表示天数,然后不断减掉对应星期几的天数即可。
  • 1e18会超时,因为每周的题数是固定的,所以单独拿出来做除法。
  • 1e18要开longlong。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL; //开ll
int main()
    LL a, b, n;  cin>>a>>b>>n;
    LL c = a*5+b*2; //每周做的题数
    LL day = n/c*7; //直接除
    n = n%c;
    while(n > 0)
        day++;
        if(day%7==6 || day%7==0)n -= b; //周末
        else n -= a;
    
    cout<<day<<"\\n";
    return 0;


第4题 —— 修剪灌木 (10分)

  • 题意:给一个数组,每秒钟所有数+1,指针开始在最左边,每秒往右移动一个,到边界后转向回来然后往复循环,求数组可能产生的最大值。
  • 其实一眼结论题
    每个灌木被剪掉以后,在下一次被剪之前,这段时间内,灌木丛会生长到最大,就是看他什么下次什么时候被剪。
    所以当前灌木丛的最大长度取决于灌木丛到两端点的距离,那么for一遍输出就行了。
#include<bits/stdc++.h>
using namespace std;
int main()
    int n;  cin>>n;
    for(int i = 1; i <= n; i++)cout<<max(i-1,n-i)*2<<endl;
    return 0;


第5题 —— X进制减法 (15分)


  • 题意:定义X进制表示每一位进制不同的数。给出两个X进制数A和B(规则相同,但是不知道规则,且最高N进制,最低2进制),求A-B的可能最小值。

  • 思路:
    因为AB的规则是一样的,所以A-B第i位的值其实已经确定了,就是给他们加个规则。
    那必然是进制越小越好,最好是2进制。
    如果都是2进制就不满足规则了,所以最小进制肯定是右边i+1上A和B的最大+1,不要再大了。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1000000007, maxn = 1e5+10;
LL a[maxn], b[maxn];

int main()
    int n, an, bn;  cin>>n>>an;
    for(int i = an; i >= 1; i--)cin>>a[i];//1-n低位到高位
    cin>>bn;
    for(int i = bn; i >= 1; i--)cin>>b[i];
    LL ans = 0, base = 1;
    for(int i = 1; i <= an; i++)
        LL w = max(a[i], b[i],1LL)+1;
        ans = (ans+(a[i]-b[i])*base)%mod;
        base = base*w%mod;
    
    cout<<ans<<"\\n";
    return 0;


第6题 —— 统计子矩阵 (15分)

  • 题意:求给出的nm的矩阵里有多少个子矩阵满足和不超过k。
  • 不难想到二维前缀和,然后暴力枚举对角线,四次方可以求出所有的解。
    但是数据范围是500的,所以还要再优化一维
  • 不难想到枚举一个横坐标上的区间以后,纵坐标维度上的可以采用双指针(尺取法)来做
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 550;
LL a[maxn][maxn], b[maxn][maxn], ans;

int main()
    int n, m, k;  cin>>n>>m>>k;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            cin>>a[i][j];
            a[i][j] += a[i-1][j]+a[i][j-1]-a[i-1][j-1];//二维前缀和
        
    
    for(int l = 1; l <= m; l++)
        for(int r = l; r <= m; r++)
            for(int i = 1, j = 1; i <= n; i++)   //双指针
                while(j <= i && (a[i][r]-a[i][l-1]-a[j-1][r]+a[j-1][l-1])>k)
                    j++;
                
                if(j <= i)ans += i-j+1;
            
        
    
    cout<<ans<<"\\n";
    return 0;


第7题 —— 积木画 (20分)


  • 题意:有2种积木,2xn的板子,求有多少放法。
  • 明显是个dp,想一下状态,令f(i,j) 表示前i-1列填满且第i列的状态为j,j=0(00) j=1(10) j=2(01) j=3(11)时候的放法数量。线性+2维的复杂度也是刚刚好。
  • 然后转移的时候尝试把各种格子放进去即可。
  • 这题一个比较坑的点是直接开longlong会爆内存,要int然后转longlong,内存卡的有点紧。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e7+10, mod = 1e9+7;
int f[maxn][4];

int main()
    int n;  cin>>n;
    f[0][3] = 1;
    f[1][0] = 1; f[1][1] = 1; f[1][2] = 1; f[1][3] = 2;
    for(int i = 2; i <= n; i++)
        f[i][0]=(LL)(f[i-1][3])%mod;
        f[i][1]=(LL)(f[i-1][0]+f[i-1][2])%mod;
        f[i][2]=(LL)(f[i-1][1]+f[i-1][0])%mod;
        f[i][3]=((LL)(f[i-1][1]+f[i-1][2])%mod+(LL)(f[i-1][3]+f[i-2][3])%mod)%mod;
    
    cout<<f[n][0]<<endl;
    return 0;


第8题 —— 扫雷 (20分)


  • 题意:给出n个地雷,坐标x,y和半径r的圆。接下来m个排雷火箭,也是坐标x,y和半径r的圆。排雷火箭范围内的地雷会爆炸,然后爆炸的地雷范围内的地雷也会爆炸,求最后炸了多少个地雷。
  • 注意到所有点爆炸半径都不超过10,而且每个点坐标都是整数,那么对于一个点其半径内的点完全可以枚举出来,最多也就400个点,n是5e4,乘起来相当于遍历一遍所有可能的点,复杂度也够。
  • 坐标直接用map存的话带个log会超时,所以要用无向的map(加个哈希)
#include<bits/stdc++.h>
using namespace std;
inline long long qpow(long long x) return x * x; 

struct hsh 
    size_t operator () (const pair<int,int> &a) const 
        return a.first * 239 + a.second * 7; 
    
;
unordered_map< pair<int,int>, int, hsh> mp;
unordered_map< pair<int,int>, int, hsh> rr;
long long ans = 0;

void dfs(int x, int y, int r)
    for(int i = x-r; i <= x+r; i++)  //枚举半径范围内的所有点
        for(int j = y-r; j <= y+r; j++)
            if(qpow(i-x)+qpow(j-y) <= qpow(r))
                auto it = make_pair(i,j);
                if(mp.count(it)) //判断是不是雷
                    ans += mp[it];
                    mp.erase(it);
                    dfs(i,j,rr[it]); //递归爆炸
                
            
        
    


int main()
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;  cin>>n>>m;
    for(int i = 1; i <= n; i++)
        int x, y, r;  cin>>x>>y>>r;
        auto pos = make_pair(x,y);
        mp[pos]++;
        rr[pos] = max(r, rr[pos]);
    
    while(m--)
        int x, y, r;  
        cin>>x>>y>>r;
        dfs(x, y, r); //从这里开始爆炸
    
    cout<<ans<<"\\n";
    return 0;




第9题 —— 李白打酒加强版 (25分)

  • 题意:开始有2斗酒, 逢店加一倍, 遇花喝一斗。一共遇到店 N 次, 遇到花 M 次。已知最后一次遇到的是花, 他正好把酒喝光了。求一路遇到店和花的顺序, 有多少种不同的可能?
    没酒 ( 0 斗) 时遇店是合法的, 加倍后还是没酒; 但是没酒时遇 花是不合法的。
  • 明显也是dp,比积木还简单一点,据说是有原题。令 f[i][j][k] 表示走过了 i 个酒馆,j 个花,还有 k 斗酒的方案数, 此时复杂度刚刚好。
  • 转移:到酒馆时,因为到酒馆时翻一倍,所以这时的 k 一定是偶数。遇到花时,上一次的酒比当前多 1,所以从k+1转移。
  • 可以用记忆化写,看起来清楚一点。
#include<bits/stdc++.h>
using namespace std;
int n, m,dp[110][110][110];

int dfs(int i, int j, int k)//走过了i个酒馆,j个花,还有k斗酒的方案数
    if(i<0 || j<0 || k<0)return 0;//遇花,遇店,酒壶里的酒都要>=0
    if(i>k)return 0; //每次要喝1,所以酒馆要小于总酒量
    if(j==0 && i==1 && k==1)return 1; //边界状态,一个酒馆
    if(dp[i][j][k] != -1)return dp[i][j][k];
    return dp[i][j][k] = (dfs(2*i,j-1,k)+dfs(i-1,j,k-1))%1000000007;


int main()
    memset(dp, -1, sizeof(dp));
    cin>>n>>m;
    cout<<dfs(2,n,m)<<"\\n";
    return 0;



第10题 —— 砍竹子 (25分)


  • 题意:一排n个竹子,每次可以选一个高度相等的区间变成根号hi/2+1下取整,求最少多少次能让所有竹子的高度变为1。
  • 数据范围 2e5,暴力肯定过不去,估计nlogn。因为是根号,所以把一根竹子变成1最多只要操作6次,所以到时候肯定会有很多相等的区间的竹子,我们不妨贪心的从高到低砍竹子(这样肯定不会更坏)。
  • 此时不难想到用堆维护,我们每次找出最高的竹子,把相同高度且编号连续的竹子依次取出(所以要重载结构体维护一个编号),然后把竹子砍一半再加入堆中。直到最高的竹子的高度是1就代表我们已经结束了。
  • 用longlong的时候,sqrt要换成sqrtl!!不然会爆精度,其他类似的函数也是。

总结2点:
1、首先我们注意到,一般的做题过程就是,找到某个结论(关键点,突破口),然后做优化的那种题目,比如说数据范围1e5,但是某个数据很小(发现6次就砍完,地雷范围r<10),就可以拿这个做文章,再比如一眼暴力,然后想想怎么用结论(比如说来回走动的规律,到两边的距离,双指针)这种来优化,或者说其实大多数dp类型的思维/暴力题,都有点这种意味在里面。
2、另外一个思维的切入点是,先想清楚再写代码,think twice, code once,这样不容易思路被带偏,不要上来就啪啪啪输入输出,这样后面就卡住了(起码我是这样的,会被打断,可能题意都忘记了,那还写个锤子),大体思路想好,感觉方案可行了,优化完了再写,细节具体再补充上去就行,不要上来就暴力,不是每道题都有写暴力对拍的时间成本的。 虽然在写不出来了的时候多交一个暴力骗分也是一个好习惯。

#include<bits/stdc++.h>
using namespace std;
typedef 第1题 —— 门牌制作 (5分) 

  • 枚举1到2020,判断有多少个字符2。
  • 答案624
#include<bits/stdc++.h>
using namespace std;

int main()
    int cnt = 0;
    for(int i = 1; i <= 2020; i++)
        int x = i;
        while(x)
            if(x%10==2)cnt++;
            x /= 10;
        
    
    cout<<cnt<<"\\n";
    return 0;



第2题 —— 既约分数 (5分)

  • 双层循环枚举1到2020,gcd分子分母=1的累加
  • 答案为2481215
#include<bits/stdc++.h>
using namespace std;

int main()
    int cnt = 0;
    for(int i = 1; i <= 2020; i++)
        for(int j = 1; j <= 2020; j++)
            if(__gcd(i,j)==1)cnt++;
        
    
    cout<<cnt<<"\\n";
    return 0;



第3题 —— 蛇形填数 (10分)

  • 发现每次对角线值的差是4的倍数,可以通过递推的方式计算
  • 答案761
#include<bits/stdc++.h>
using namespace std;

int main()
    int x = 1;
    //+4->2,+8->3,+19->20
    for(int i = 1; i <= 19; i++)
        x += i*4;
    
    cout<<x;
    return 0;



第4题 —— 跑步锻炼 (10分)

  • 暴力遍历每一天,算出每一天的是星期几并累加跑多少,注意判断闰年
  • 答案8879
#include<bits/stdc++.h>
using namespace std;
int	a[13]=0,31,28,31,30,31,30,31,31,30,31,30,31;
int main()
    int sum = 0, week = 6;
    for(int year=2000; year <= 2020; year++)
        if((year%400==0) || (year%4==0&&year%100!=0))a[2] = 29;
        else a[2] = 28;
        for(int month = 1; month <= 12; month++)
            for(int day = 1; day<=a[month]; day++)
                if(day==1 || week==1)sum += 2;
                else sum += 1;
                week = (week+1)%7;
                if(year==2020 && month==10 && day==1)
                    cout<<sum<<'\\n';
                    return 0;
                
            
        
    
    return 0;



第5题 —— 七段码 (15分)


  • 必须要相邻才能发光,也就是所有开着的灯必须是连通的,求合法的方案数。
  • 建图后dfs选边,选完判断连通性统计答案。
  • 答案80。
#include<bits/stdc++.h>
using namespace std;

int fa[10];
int find(int x) return x==fa[x]?x:fa[x]=find(fa[x]); 

int e[10][10], ans;
int vis[10];
void dfs(int cur)
    if(cur == 8)
        for(int i = 1; i <= 7; i++)fa[i] = i;
        for(int i = 1; i <= 7; i++)
            for(int j = 1; j <= 7; j++)
                if(e[i][j] && vis[i] && vis[j])
                    int x = find(i), y = find(j);
                    if(x != y)
                        fa[x] = y;
                    
                
            
        
        int cnt = 0;
        for(int i = 1; i <= 7; i++)
            if(vis[i] && i==fa[i])cnt++;
        
        if(cnt == 1)ans++;
    else
        vis[cur] = 1;
        dfs(cur+1);
        vis[cur] = 0;
        dfs(cur+1);
    


int main()
    //连边建图
    //a b c d e f g
    //1 2 3 4 5 6 7
    e[1][2] = e[1][6] = 1;
    e[2][1] = e[2][7] = e[2][3] = 1;
    e[3][2] = e[3][4] = e[3][7] = 1;
    e[4][3] = e[4][5] = 1;
    e[5][4] = e[5][6] = e[5][7] = 1;
    e[6][1] = e[6][5] = e[6][7] = 1;

    dfs(1);
    cout<<ans<<"\\n";
    return 0;



第6题 —— 成绩统计 (15分)

  • 计算及格率和优秀率,四舍五入输出。
#include<bits/stdc++.h>
using namespace std;

int main()
    int n;  cin>>n;
    int a = 0, b = 0;
    for(int i = 1; i <= n; i++)
        int x;  cin>>x;
        if(x>=60)a++;
        if(x>=85)b++;
    
    printf("%.0lf%%\\n%.0lf%%", round(a*100.0/n), round(b*100.0/n));
    return 0;


第7题 —— 回文日期 (20分)

  • 逐个枚举判断八位数是不是回文是会超时的。
  • 我们可以枚举1000到8999前四位,然后直接构造回文串,根据输入二分查询。
  • 注意闰年和abab格式
//AC
#include<bits/stdc++.h>
using namespace std;
string to_string(int x)
    string res;
    stringstream ss;
    ss<<x;
    ss>>res;
    return res;

int stoi(string x)
    int res;
    stringstream ss;
    ss<<x;
    ss>>res;
    return res;


int a[13] =  0, 31,28,31,30,31,30,31,31,30,31,30,31 ;
int main()
    vector<int>vc, vc2; //存起来
    for(int i = 1000; i <= 9999; i++)//枚举年
        string s = to_string(i);
        for(int j = 3; j >= 0; j--)s+=s[j];//构造abab
        int yy = i;
        int mm = (s[4]-'0')*10+(s[5]-'0');
        int dd = (s[6]-'0')*10+(s[7]-'0');
        int ok;//确保日期合法
        if(mm >= 1 && mm <= 12)
            if((yy%400==0) || (yy%4 == 0 && yy %100!=0))a[2] = 29;
            else a[2] = 28;
            if(a[mm]>=dd)ok = 1;
            else ok = 0;
        else
            ok = 0;
        
        //确保日期是回文
        if(ok)
            vc.push_back(stoi(s));
        
        //确保日期是abab
        if(s[0]==s[2] && s[1]== s[3]);
        else ok = 0;
        if(ok)
            vc2.push_back(stoi(s));
        
    
    int x;  cin>>x;
    int p1 = upper_bound(vc.begin(),vc.end(), x)-vc.begin();
    int p2 = upper_bound(vc2.begin(),vc2.end(), x)-vc2.begin();
    cout<<vc[p1]<<"\\n";
    cout<<vc2[p2]<<"\\n";
    return 0;



第8题 —— 子串分值和 (20分)

  • 暴力不难想到双循环枚举起点和长度,每次计算,50分。
  • 考虑每个字符的贡献,仅在与上一个与之相同的字符中间处产生贡献,直接统计。
#include<bits/stdc++.h>
using namespace std;

int main()
    string s;  cin>>s;
    int z[50] = 0; long long ans = 0;
    for(int i = 0; i < s.size(); i++)
        z[s[i]-'a'] = i+1;
        for(int i = 0; i < 27; i++)
            ans += z[i];
        
    
    cout<<ans<<'\\n';
    return 0;



第9题 —— 平面切分 (25分)

  • 直线相交产生平面个数,是个结论。即第n条直线与前n-1条相交时,如果产生了n-1个交点,那么会多出来n个平面。
  • 开个set维护直线集合,每次暴力枚举求交点,+1累积即可。
#include<bits/stdc++.h>
using namespace std;

set<pair<double, double> >se;//存直线
int calc(double c, double d)//求新产生的交点个数
    set<pair<double, double> >pp;//存交点
    set<pair<double, double> >::iterator i;
    for(i = se.begin(); i != se.end(); i++)
        double a = i->first, b = i->second;
        if(a!=c)
            pair<double, double> t;//求交点
            t.first = (d-b)/(a-c); 
            t.second = c*t.first+d;
            pp.insert(t);
        
    
    return pp.size();


int main()
    int n;  cin>>n;
    int res = 1;
    for(int i = 1; i <= n; i++)
        int a, b;  cin>>a>>b;
        pair<double, double>t;
        t.first = a; t.second = b;
        if(!se.count(t))//没有重复
            res++;
            res += calc(a,b);
        
        se.insert(t);
    
    cout<<res<<'\\n';
    return 0;



第10题 —— 字串排序 (25分)

  • 冒泡排序的交换次数——序列中逆序对的个数。
    在满足交换次数V的前提下,要求最短的序列。
  • 可以按照字典序暴力枚举字符串,然后每次求逆序对判断>=V。

以上是关于2022 第十三届蓝桥杯大赛软件赛省赛,C/C++ 大学B组题解的主要内容,如果未能解决你的问题,请参考以下文章

第十三届蓝桥杯大赛软件赛省赛(C/C++ 大学B组)

第十三届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组思考+总结

第十三届蓝桥杯大赛软件赛省赛(Java 大学A组)

2020 第十一届蓝桥杯大赛软件赛省赛(第一场),C/C++大学B组题解

2019 第十届蓝桥杯大赛软件赛省赛,C/C++大学B组题解

2019 第十届蓝桥杯大赛软件赛省赛,C/C++大学B组题解