第十一届蓝桥杯C/C++A组省赛全题解

A.门牌制作(5分,填空题)

【问题描述】

    小蓝要为一条街的住户制作门牌号。

    这条街一共有 2020 位住户,门牌号从 1 到 2020 编号。

    小蓝制作门牌的方法是先制作 0 到 9 这几个数字字符,最后根据需要将字符粘贴到门牌上,例如门牌 1017 需要依次粘贴字符 1、0、1、7,即需要 1 个字符 0,2 个字符 1,1 个字符 7。请问要制作所有的 1 到 2020 号门牌,总共需要多少个字符 2?

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n=1,all=0;
    while(n<=2020){   
        int x=n;
        while(x){
            int r=x%10;
            if(r==2) all++;
            x=x/10;
        }
        n++;
    }
    cout<<all<<endl;
    return 0;
}

难度:签到题(简单)

题解:枚举,统计每一个门牌号字符2的个数

答案:624


B.既约分数(5分,填空题)

【问题描述】

    如果一个分数的分子和分母的最大公约数是 1,这个分数称为既约分数。
    例如,3/4 , 5/2 , 1/8 , 7/1 都是既约分数。
    请问,有多少个既约分数,分子和分母都是 1 到 2020 之间的整数(包括 1 和 2020)?

#include<bits/stdc++.h>
using namespace std;

int gcd(int i,int j){          //计算最大公约数
    if(i%j==0) return j;
    else return gcd(j,i%j);
}

int main(){
    int sum=0;
    for(int i=1;i<=2020;i++)
        for(int j=1;j<=2020;j++){
            if(gcd(i,j)==1) sum++;
        }
    cout<<sum<<endl;
    return 0;
}

难度:签到题(简单)

题解:枚举,判断分子和分母是不是互质

答案:2481215


C.蛇形填数(10分,填空题)

【问题描述】

    如下图所示,小明用从 1 开始的正整数“蛇形”填充无限大的矩阵。
    1 2 6 7 15 …
    3 5 8 14 …
    4 9 13 …
    10 12 …
    11 …
    …
    容易看出矩阵第二行第二列中的数是 5。请你计算矩阵中第 20 行第 20 列
的数是多少?

#include<bits/stdc++.h>
using namespace std;
int f[40];

int main(){
    f[1]=1;
    for(int i=2;i<=39;i++){
        f[i]=f[i-1]+i;
}
    int x=(f[39]+f[38]+1)/2;
    cout<<x<<endl;
    return 0;
}

难度: 适中

题解:找规律,矩阵中间的值为对角线两端和的均值(2n+1个数情况下)。第n条对角线有n个数,该对角线上的最大值是1+2+...+n。可以判断第20行第20列的数在第39条斜对角线,则该对角线的中间值为(第39条对角线最大值+第38条对角线最大值+1)/2

答案:761


D.7段码(10分,填空题)

【问题描述】

    小蓝要用七段码数码管来表示一种特殊的文字。

    七段码上图给出了七段码数码管的一个图示,数码管中一共有 7 段可以发光的二极管,分别标记为 a, b, c, d, e, f, g。小蓝要选择一部分二极管(至少要有一个)发光来表达字符。在设计字符的表达时,要求所有发光的二极管是连成一片的。

    例如:b 发光,其他二极管不发光可以用来表达一种字符。

    例如:c 发光,其他二极管不发光可以用来表达一种字符。这种方案与上一行的方案可以用来表示不同的字符,尽管看上去比较相似。

    例如:a, b, c, d, e 发光,f, g 不发光可以用来表达一种字符。

    例如:b, f 发光,其他二极管不发光则不能用来表达一种字符,因为发光的二极管没有连成一片。

    请问,小蓝可以用七段码数码管表达多少种不同的字符?

#include<bits/stdc++.h>  //递归枚举+并查集
using namespace std;
int book[10],res=0,f[10];
int u[10]={0,1,2,3,4,5,1,3}; //存边的端点
int v[10]={0,2,3,4,5,6,6,6};
int find(int x)
{
    return x == f[x] ? x : (f[x] = find(f[x]));
}
void check()
{
    int pos=-1;
    for(int i=1;i<=6;i++) f[i]=i;  //并查集 初始化
    for(int i=1;i<=7;i++)
        if(book[i])  //如果选上了就连起来
            f[find(u[i])]=find(v[i]),pos=u[i];
    if(pos==-1) return;
    for(int i=1;i<=7;i++)  //有几个根节点说明有几个集合:判断根节点是否相同,检查两个元素是否在同一个集合里
        if(book[i]&&find(u[i])!=find(pos))
            return ;
    res++;  //在同一个集合里
    return;
}
void dfs(int x)
{
    if(x==8){
        check();
        return ;
    }
    book[x]=1; dfs(x+1);  //选上第x条边
    book[x]=0; dfs(x+1);  //不选
}
int main()      // 80
{
    dfs(1); //暴力枚举七条边是否选取  2^7次方种情况
    cout<<res<<endl;
    return 0;
}

难度:较难

题解:递归枚举,七条边+选/不选,利用类似二进制的方式,得出所有的可能排列方式,再用递归的方式判断是否可以连成一片;并查集,根节点,合并(路径压缩)。

答案:80


E.平面分割(15分,填空题)

【问题描述】

    20 个圆和20 条直线最多能把平面分成多少个部分?

#include <bits/stdc++.h>
using namespace std;
int main(){
    int m=20,n=20;
    cout<<m*m-m+2*m*n+2*n+(n-1)*(n-2)/2;
    return 0;
}

难度:较难

题解:数学题,直线和封闭曲线分割平面。

假设平面上现在已经有将平面切割成g(m)块的m个圆形,我们在此基础上再考虑添加直线:

$h(0)=g(m)=m^2-m+2$

每次加入一条直线,让直线和每一个圆都相交,那么会产生2m个交点,会将直线分为2m-1个线段和2条射线,一共将会新增加2m个部分,也就是说每一条直线加入只考虑被原有的圆形切割的话都会新增加2m个部分:

$h(1) = h(0) + 2*m = 2 + (m-1) m + 2m$;

加入第二条直线,因被所有圆切割而增加2*m个部分,因被前一条直线切割而增加2个部分:

$h(2) = h(1) + (2m + 2)$;

以此类推:h(n) = h(n-1) + (2*m+n)
     = h(n-2) + (2*m+(n-1)) + (2*m+n)
     ......
     = h(1) + (2*m+(2)) ... + (2*m+n)
     = (2 + (m-1) *m) + (2m*n) + 2 + 3 ... + n
     = m*m - m + 2m*n + 1 + n*(n+1) / 2

答案:1391


F.成绩统计(15分,编程题)

【问题描述】

    小蓝给学生们组织了一场考试,卷面总分为100 分,每个学生的得分都是一个0 到100 的整数。请计算这次考试的最高分、最低分和平均分。

【输入格式】

    输入的第一行包含一个整数n,表示考试人数。

    接下来n 行,每行包含一个0 至100 的整数,表示一个学生的得分。

【输出格式】

输出三行。

    第一行包含一个整数,表示最高分。

    第二行包含一个整数,表示最低分。

    第三行包含一个实数,四舍五入保留正好两位小数,表示平均分。

【样例输入】

7
80
92
56
74
88
99
10

【样例输出】

99
10
71.29

【评测用例规模与约定】
    对于50% 的评测用例, 1 ≤ n ≤ 100。
    对于所有评测用例,1 ≤ n ≤10000。

#include<bits/stdc++.h>
using namespace std;
int main(){
    double n;
    cin>>n;
    double maxx=0,minn=100,sum=0;
    for(int i=1;i<=n;i++){
        double x;
        cin>>x;
        sum+=x;
        if(x>maxx) maxx=x;
        if(x<minn) minn=x;
    }
    double ave=sum/n+0.005;
    cout<<maxx<<endl;
    cout<<minn<<endl;
    cout<<fixed<<setprecision(2)<<ave;
    return 0;
}

难度:签到题(简单)

题解:模拟题,基本的输入输出,注意四舍五入并使用浮点型数据


G.回文日期(20分,编程题)

【问题描述】

    2020 年春节期间,有一个特殊的日期引起了大家的注意:2020年2月2日。因为如果将这个日期按“yyyymmdd” 的格式写成一个8 位数是20200202,恰好是一个回文数。我们称这样的日期是回文日期。

    有人表示20200202 是“千年一遇” 的特殊日子。对此小明很不认同,因为不到2年之后就是下一个回文日期:20211202 即2021年12月2日。

    也有人表示20200202 并不仅仅是一个回文日期,还是一个ABABBABA型的回文日期。对此小明也不认同,因为大约100 年后就能遇到下一个ABABBABA 型的回文日期:21211212 即2121 年12 月12 日。算不上“千年一遇”,顶多算“千年两遇”。

    给定一个8 位数的日期,请你计算该日期之后下一个回文日期和下一个ABABBABA型的回文日期各是哪一天。

【输入格式】

    输入包含一个八位整数N,表示日期。

【输出格式】

    输出两行,每行1 个八位数。第一行表示下一个回文日期,第二行表示下一个ABABBABA 型的回文日期。

【样例输入】

20200202

【样例输出】

20211202
21211212

【评测用例规模与约定】

    对于所有评测用例,10000101 ≤ N ≤ 89991231,保证N 是一个合法日期的8位数表示。

#include<bits/stdc++.h>
using namespace std;
int n,y[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool print(int x)
{
    int a[5],now=x,tmp=1;
    for(int i=1;i<=4;i++)
        a[i]=now%10,now/=10;
    x*=10000;
    for(int i=4;i>=1;i--)
        x+=tmp*a[i],tmp*=10;
    if(x<=n) return 0;
    cout<<x<<endl;
    return 1;
}
bool run(int x)
{
    if(x%100==0&&x%400==0) return 1;
    if(x%100!=0&&x%4==0) return 1;
    return 0;
}
bool check1(int x)  //检查日期是否合法
{//已经保证了回文 只需要保证日期合法即可
    int yue=(x%10)*10+(x/10)%10; //提取日期
    int day=((x/100)%10)*10+(x/1000);
    if(yue<=0||yue>=13) return 0; //检查yue是否合法
    if(yue==2&&run(x))  //检查day是否合法
    {
        if(day<=29) return 1;
        return 0;
    }
    else if(day<=y[yue]&&day>=1) return 1;
    return 0;
}
bool check2(int x)
{//同样已经保证了回文  可以利用check1检查合法日期
    if(check1(x)==0) return 0;  //首先保证日期合法
    int a=(x/10)%10,b=x%10;     //检查ABAB
    if( x/1000==a && ((x/100)%10)==b ) return 1;
    return 0;
}
void solve(int x)
{
    for(int i=x;i<=9999;i++)
        if(check1(i)){ //找回文
            if(print(i)==0) continue;
            break;
        }
    for(int i=x;i<=9999;i++)
        if(check2(i)){  //找ABAB
            if(print(i)==0) continue;
            break;
        }
}
int main()
{
    cin>>n;
    solve(n/10000);
    return 0;
}

难度:较难

题解:枚举+模拟。枚举每一个日期,将日期取余,得到对应的月份和日期,把每一位存入数组,判断对应为是否相等以及日期是否合法


H.子串分值(20分,编程题)

【问题描述】   

    对于一个字符串S,我们定义S 的分值 f(S)为S中恰好出现一次的字符个数。例如f (”aba”) = 1,f (”abc”) = 3, f (”aaa”) = 0。

    现在给定一个字符串S[0…n-1](长度为n),请你计算对于所有S的非空子串S[i…j](0 ≤ i ≤ j < n),f (S[i… j])的和是多少。

【输入格式】

    输入一行包含一个由小写字母组成的字符串S。

【输出格式】

    输出一个整数表示答案。

【样例输入】

ababc

【样例输出】

21

【样例说明】

子串   f值:
a       1
ab      2
aba     1
abab    0
ababc   1
 b      1
 ba     2
 bab    1
 babc   2
  a     1
  ab    2
  abc   3
   b    1
   bc   2
    c   1

【评测用例规模与约定】

    对于20% 的评测用例,1 ≤ n ≤ 10;

    对于40% 的评测用例,1 ≤ n ≤ 100;

    对于50% 的评测用例,1 ≤ n ≤ 1000;

    对于60% 的评测用例,1 ≤ n ≤ 10000;

    对于所有评测用例,1 ≤ n ≤ 100000。

#include<bits/stdc++.h> 
using namespace std;
const int maxn=1e5+5;
char s[maxn];
int pos[30],nx[maxn],pre[maxn];
long long res=0;
int main()
{
    cin>>s+1; 
    int len=strlen(s+1);
    for(int i=1;i<=len;i++)
    {
        pre[i]=pos[s[i]-'a'];
        pos[s[i]-'a']=i;
    }
    for(int i=0;i<=26;i++) pos[i]=len+1;
    for(int i=len;i>=1;i--)
    {
        nx[i]=pos[s[i]-'a'];
        pos[s[i]-'a']=i;
    }
    for(int i=1;i<=len;i++)
        res+=1LL*(i-pre[i])*(nx[i]-i);
    cout<<res<<endl;
    return 0;
}

难度:困难

题解:枚举规律,单独计算每一个点的贡献;预处理数组,每个位置的特定的产物和规律。一个字母有效次数是:(该字母位置-前一个相同字母位置)*(下一个相同字母位置-当前字母位置),其中将字符串c[0]区域空出或者i从1开始提取字母i-1,视该区域为26个字母都存在但不计算,从c[n]即字符串最后一个字符后一位进行相同处理。


I.荒岛探测(25分,编程题)

【问题描述】   

    科学家小蓝来到了一个荒岛,准备对这个荒岛进行探测考察。小蓝使用了一个超声定位设备来对自己进行定位。为了使用这个设备,小蓝需要在不同的点分别安装一个固定的发射器和一个固定的接收器。小蓝手中还有一个移动设备。定位设备需要从发射器发射一个信号到移动设备,移动设备收到后马上转发,最后由接收器接收,根据这些设备之间传递的时间差就能计算出移动设备距离发射器和接收器的两个距离,从而实现定位。

    小蓝在两个位置已经安装了发射器和接收器,其中发射器安装在坐标 (xA,yA),接收器安装在坐标 (xB,yB)。小蓝的发射器和接收器可能在岛上,也可能不在岛上。小蓝的定位设备设计有些缺陷,当发射器到移动设备的距离加上移动设备到接收器的距离之和大于L 时,定位设备工作不正常。当和小于等于L 时,定位设备工作正常。为了安全,小蓝只在定位设备工作正常的区域探测考察。

    已知荒岛是一个三角形,三个顶点的坐标分别为 (x1,y1),(x2,y2),(x3,y3)。

    请计算,小蓝在荒岛上可以探测到的面积有多大?

【输入格式】

    输入的第一行包含五个整数,分别为xA,yA,xB,yB,L。

    第二行包含六个整数,分别为x1,y1,x2,y2,x3,y3。

【输出格式】

    输出一行,包含一个实数,四舍五入保留2位小数,表示答案。

    考虑到计算中的误差,只要你的输出与参考输出相差不超过0.01即可得分。

【样例输入】

10 6 4 12 12
0 2 13 2 13 15

【样例输出】

39.99

【样例说明】

    荒岛的形状和定位设备工作正常的区域如下图所示,蓝色的三角形表示荒岛,红色的曲线围成的区域为定位设备工作正常的区域。

   

    当输出为39.98、39.99或40.00时可以得分。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const double pi=acos(-1);
const double eps=1e-8;
double xa,ya,xb,yb,l,x[5],y[5],_y[5],res=0,site;
double dis(double a,double b,double c,double d){
    return sqrt((c-a)*(c-a)+(d-b)*(d-b));
}
bool findtuo(double now){
    double jx=(xa+xb)/2,jy=(ya+yb)/2;
    double aa=l/2;
    double cc=dis(xa,ya,xb,yb)/2;
    double bb=sqrt(aa*aa-cc*cc);
    double mid=1.0-(now-jx)*(now-jx)/(aa*aa);
    if(mid<=0) return 0;
    mid=sqrt(mid)*bb;
    _y[1]=jy-mid,_y[2]=jy+mid;
    return 1;
}
double check(double now,int pos1,int pos2){
    if( fabs(x[pos1]-x[pos2])<=eps && fabs(x[pos1]-now)<=eps ) return 1001.0;
    if(now<=min(x[pos1],x[pos2])||now>=max(x[pos1],x[pos2])) return 1001.0;
    double k=(y[pos2]-y[pos1])/(x[pos2]-x[pos1]);
    return k*(now-x[pos2])+y[pos2];
}
bool findsan(double now){
    int cnt=2;
    for(int i=1;i<=3;i++)
    {
        int nx=i+1;
        if(nx==4) nx=1;
        double mid=check(now,i,nx);
        if(mid>=1000.5) continue;
        if(cnt==3&&fabs(mid-_y[cnt])<=eps) continue;
        _y[++cnt]=mid;
    }
    if(cnt!=4) return 0;
    return 1;
}
double anglee(double a,double b,double c,double d){
    double now=acos( (c-a) / dis(a,b,c,d) );
    if(d-b<0) return pi+pi-now;
    return now;
}
void zhuan(int pos)
{
    double sitepos=anglee(0,0,x[pos],y[pos]);
    double val=dis(0,0,x[pos],y[pos]);
    double diff=sitepos-site;
    x[pos]=val*cos(diff);
    y[pos]=val*sin(diff);
    //printf("%lf %lf\n",x[pos],y[pos]);
}
void zhuantuo()
{
    double sitepos1=anglee(0,0,xa,ya),sitepos2=anglee(0,0,xb,yb);
    double val1=dis(0,0,xa,ya),val2=dis(0,0,xb,yb);
    double diff1=sitepos1-site,diff2=sitepos2-site;
    //printf("%lf %lf %lf\n",site,sitepos1,sitepos2);
    xa=val1*cos(diff1); ya=val1*sin(diff1);
    xb=val2*cos(diff2); yb=val2*sin(diff2);
    //printf("%lf %lf %lf %lf\n",xa,ya,xb,yb);
}
double solve(double now)
{
    if(findtuo(now)==0) return 0;
    if(findsan(now)==0) return 0;
    double mx=max(min(_y[1],_y[2]),min(_y[3],_y[4]));
    double mn=min(max(_y[1],_y[2]),max(_y[3],_y[4]));
    //printf("%.4lf %.4lf %.4lf %.4lf %.4lf %.4lf\n",now,_y[1],_y[2],_y[3],_y[4],mn-mx);
    if(mn-mx<=eps) return 0;
    return mn-mx;
}

int main()
{
    scanf("%lf%lf%lf%lf%lf",&xa,&ya,&xb,&yb,&l);
    site=anglee(xa,ya,xb,yb);
    for(int i=1;i<=3;i++) scanf("%lf%lf",&x[i],&y[i]),zhuan(i);
    zhuantuo();
    if(l<=eps||dis(xa,ya,xb,yb)>=l) return 0*printf("0.00\n");
    for(double i=-1000;i<=1000;i+=0.001)
        res+=solve(i)*0.001;
    printf("%.2lf\n",res);
    return 0;
}

难度:困难

题解:扫描。利用微分精度求面积的思想,将椭圆形和三角形的旋转使椭圆形实轴水平,从椭圆形最左端开始逐次扫描0.001的长度,将椭圆内的合法长度*0.001.


J.字串排序(25分,编程题)

【问题描述】   

    小蓝最近学习了一些排序算法,其中冒泡排序让他印象深刻。

    在冒泡排序中,每次只能交换相邻的两个元素。

    小蓝发现,如果对一个字符串中的字符排序,只允许交换相邻的两个字符,则在所有可能的排序方案中,冒泡排序的总交换次数是最少的。

    例如,对于字符串 lan 排序,只需要 1 次交换。对于字符串 qiao 排序,总共需要 4 次交换。

    小蓝找到了很多字符串试图排序,他恰巧碰到一个字符串,需要 V 次交换,可是他忘了把这个字符串记下来,现在找不到了。

    小蓝的幸运数字是V,他想找到一个只包含小写英文字母的字符串,对这个串中的字符进行冒泡排序,正好需要V次交换。请帮助小蓝找一个这样的字符串。如果可能找到多个,请告诉小蓝最短的那个。如果最短的仍然有多个,请告诉小蓝字典序最小的那个。请注意字符串中可以包含相同的字符。

【输入格式】

    输入的第一行包含一个整数V,小蓝的幸运数字。

【输出格式】

    题面要求的一行字符串。

【样例输入】

4

【样例输出】

bbaa

【样例输入】

100

【样例输出】

jihgfeeddccbbaa

【评测用例规模与约定】

    对于30%的评测用例,1 ≤ V ≤ 20;

    对于50%的评测用例,1 ≤ V≤ 100;

    对于所有的评测用例,1 ≤ V ≤ 10000;

#include <bits/stdc++.h>
using namespace std;
const int N = 135, M = 10010;
int f[N][30][N];
//chcnt[i][j]记录第i个位置取字母j+'a'的逆序对最大值 
int chcnt[N][30];
//mlen[i]记录每个位置的最大值 
int mlen[N];
void dp()
{
	for (int i=2;i<N;++i)
	{
		int m=0;
		for (int j=1; j<='z'-'a';++j)
		{
			for (int k=1; k<i;++k)
			{
				if (k > 1) f[i][j][k] = f[i-1][j][k-1]+i-k;
				else f[i][j][k] = chcnt[i-1][j-1]+i-1;
				chcnt[i][j] = max(chcnt[i][j], f[i][j][k]);
			}
			m = max(m, chcnt[i][j]);
		}
		mlen[i]=m;
	}
}
int main()
{
	dp();
	int score=0;
	cin >> score;
	//找出最短长度值
	int beg=0;
	for (int i=1; i<N; ++i)
		if (mlen[i]>=score)
		{
			beg=i;
			break;
		}
	int curr=0;	//用于记录逆序值
	int same=1;	//记录后缀中有多少个相同字母
	char last='z' + 1;//记录上一个字母是什么 
	for (int i=beg;i>0;--i)
	{
		//从a开始枚举
		int j=0;
		for (;j<=last-'a';++j)
		{
			if (j==last-'a') curr-=same;
			if (curr+chcnt[i][j]>=score)
			{
				curr+=i-1;
				break;
			}
		}
		if (j==last-'a') same++;
		else
		{
			last=j+'a';
			same=1;
		}
		cout<<last;
	}
	cout<<endl;
	return 0;
}

难度:困难

题解:贪心算法,求字典序最小的序列(可以直接打表骗分)

蓝桥杯历届真题地址:www.lanqiao.cn/courses/2786 


  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值