2020年第11届蓝桥杯C/C++ B组省赛(第二场)做题记录

鉴于本人将参加第12届蓝桥杯,所以从网上找来第11届真题做,发现自己还有许多不足之处,就记录一下。
试题A
思路: 编写一个函数,将一个数的各位数字分离,计算有多少个2
代码:

#include <cstdio>
#include <algorithm>

using namespace std;
int numoftwo(int n) {
    int m=0;
    while(n>0) {
        int t=n%10;
        if(t==2) {
            m++;
        }
        n/=10;
    }
    return m;
}
int main() {
    int a=2020,sum=0;
    for(int i=1; i<=a; i++) {
        int k=numoftwo(i);
        if(k!=0) {
            printf("%d %d\n",i,k);
        }
        sum+=k;
    }
    printf("%d\n",sum);
    return 0;
}

试题B思路: 暴力枚举不解释
代码:

#include <cstdio>
#include <algorithm>

using namespace std;
int gcd(int x,int y){
    int t=x%y;
    if(t==0) 
    	return y;
    gcd(y,t);
}
int main(){
    int i,j,cnt=0;
    for(i=1;i<2021;i++){
        for(j=1;j<2021;j++){
            if(gcd(i,j)==1){
               // printf("%d/%d ",i,j);
                cnt++;
            }
        }
    }
    printf("%d\n",cnt);
    return 0;
}

在这里插入图片描述
思路: 设行号,列号分别为i,j(均从0开始),矩阵用二维数组a保存。观察这个矩阵,不难看出:
1.a[0][0]=1;
2.对于每行首列元素,若i为偶数,则a[i][0]=a[i-1][0]+1,若i为奇数,则a[i][0]=a[i-1][0]+k,其中k初值为2,每次递增4;
3.对于其他元素,若i+j为奇数,则a[i][j]=a[i][j-1]+2*i-1,若为偶数,则a[i][j]=a[i][j-1]+m,当i为奇数时,m初值为2,否则初值为4,每次递增4,注意每行开始时m都要刷新。
代码

#include <cstdio>
#include <algorithm>
#define N 30
using namespace std;
bool iseven(int x) {
    return x%2==0?true:false;
}

int main() {
    int i,j,m,k=2; //m:列递增器,k:行递增器
    int a[N][N];
    a[0][0]=1;
    for(i=0;i<N;i++){
        if(iseven(i))
            m=4;
        else
            m=2;
        for(j=0;j<N;j++){
            if(j!=0){
                if(!iseven(i+j))
                    a[i][j]=a[i][j-1]+(2*i+1);
                else {
                    a[i][j]=a[i][j-1]+m;
                    m+=4;
                }
            }
            else if(i!=0){ //计算每行首列元素,注意首行不能算进去
                if(iseven(i))
                	a[i][0]=a[i-1][0]+1;
                else{
                    a[i][0]=a[i-1][0]+k;
                    k+=4;
                }
            }
        }
    }
/*    for(i=0;i<21;i++){
        for(j=0;j<21;j++)
            printf("%5d",a[i][j]);
        printf("\n");
    }*/
    printf("%d\n",a[19][19]);
    return 0;
}

试题D
思路: 发现两个公式:
1.总里程数=天数+周一数+月份数-既是周一又是每月1日的天数;
2.周一数=第一天之后第一个周一到最后一天之间的天数(包括这两天)/7+1。
接下来思路就比较顺其自然了,比较麻烦的是各种边界条件的处理。
天数用Windows自带的计算器也可算出,但注意要+1。
代码:

#include <cstdio>
#include <algorithm>

using namespace std;
int dpm[13]= {0,31,28,31,30,31,30,31,31,30,31,30,31};
bool isleap(int y) {
    if(y%4==0) {
        if(y%400==0)
            return true;
        if(y%100==0&&y%400!=0)
            return false;
    } else
        return false;
}

int days(int s_year,int e_year,int e_month,int e_d) { //计算天数,返回s_year年元旦到e_year年e_month月e_d日的天数
    int date=0,m,d;
    for(int i=s_year; i<e_year; i++) {
        if(isleap(i)) {
            dpm[2]=29;
            date+=366;
        } else {
            dpm[2]=28;
            date+=365;
        }
    }
    m=d=1; //最后一年
    if(isleap(e_year))
        dpm[2]=29;
    else
        dpm[2]=28;
    for(m=1; m<e_month; m++)
        date+=dpm[m];
    date+=e_d;
    return date;
}

int main() {
    int a=2000,b=2020,m=1,d=1,hr,nom,mth;//b:最终年份,a,m,d:当前日期,nom:星期一数,hr:总小时数,mth:总月份数
    int k=6; //k为当前星期数
    nom=(days(a,b,10,1)-2)/7+1;
    mth=12*(b-a)+9+1;
    hr=days(a,b,10)+mth+nom;
    while(a<b) { //这个只能对2000-2019去重
        if(isleap(a))
            dpm[2]=29;
        else
            dpm[2]=28;
        while(m<13) {
            while(d<=dpm[m]) {
                if(k==1&&d==1) {
//                    printf("%d.%d.%d 星期%d\n",a,m,d,k);
                    hr--; //去重
                }
                d++;
                if(d<=dpm[m])
                    k++;
                if(k==8)
                    k=1;  //返回星期一
            }
            m++;
            if(m<13) { //防止重算
                d=1;
                k++;
                if(k==8)
                    k=1;
            }
        }
        a++;
        m=d=1;
        k++;
        if(k==8)
            k=1;
    }
    printf("%d\n",hr-1); //2020.6.1也是星期一
    return 0;
}

试题E
不会做……自己硬数数出来79,但答案应该是80。看网上说要什么并查集,但本人并没有学过这个,看来还有待提升啊。
试题F
思路: 暴力,不多说,注意百分号的输出。
代码:

#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
    int a[10000],n,i,good=0,pass=0;
    double jgl,yxl;
    scanf("%d",&n);
    for(i=0; i<n; i++)
        scanf("%d",&a[i]);
    for(i=0; i<n; i++){
        if(a[i]>=60)
            pass++;
        if(a[i]>=85)
            good++;
    }
    jgl=(pass/(double)n)*100;
    yxl=(good/(double)n)*100;
    printf("%.0lf%c\n",jgl,'%');
    printf("%.0lf%c\n",yxl,'%');
    return 0;
}

试题G
思路: 同样是暴力,注意循环每执行一次就要把年月日提取出来放到数组里。用f1,f2标记两种类型的输出次数,均为1时代表输出完毕,终止循环。
有两个问题:
1.输出的“回文日期”是否应排除ABABBABA型(我认为不用);
2.11111111是否为ABABBABA型“回文日期”(我认为是的);
代码:

#include <cstdio>
#include <algorithm>

using namespace std;
int dpm[13]= {0,31,28,31,30,31,30,31,31,30,31,30,31};
int year[4],month[2],day[2],f1=0,f2=0; //f1,f2标志两种类型日期输出次数
bool isleap(int y) {
    if(y%4==0) {
        if(y%400==0||y%100!=0)
            return true;
        else if(y%100==0&&y%400!=0)
            return false;
    }
    return false;
}

void fillin(int y,int m,int d) { //填充数组
    //每加一天调用一次
    int i=3;
    while(y>0) {
        year[i]=y%10;
        y/=10;
        i--;
    }
    month[0]=m/10;
    month[1]=m%10;
    day[0]=d/10;
    day[1]=d%10;
}

bool hw(void) { //判断是否回文
    if(year[0]==day[1]&&year[1]==day[0]&&year[2]==month[1]&&year[3]==month[0])
        return true;
    return false;
}

bool hw_special(void) { //判断是否为ABABBABA型
    if(year[0]==year[2]&&year[1]==year[3]&&hw())
        return true;
    return false;
}

void print(void) {
    if(hw()&&f1==0) {
        for(int i=0; i<4; i++)
            printf("%d",year[i]);
        printf("%d%d%d%d\n",month[0],month[1],day[0],day[1]);
        f1++;
    }
    if(hw_special()) {
        for(int i=0; i<4; i++)
            printf("%d",year[i]);
        printf("%d%d%d%d\n",month[0],month[1],day[0],day[1]);
        f1++;
        f2++;
    }
}

int main() {
    int date,cy,cm,cd;
    scanf("%d",&date);
    fill(year,year+4,0);
    fill(month,month+2,0);
    fill(day,day+2,0);
    cy=date/1e4;
    cd=date%100;
    cm=date/100-cy*100;
    fillin(cy,cm,cd);
    //printf("%d.%d.%d\n",cy,cm,cd);
    while(f1<=1&&f2<=1) {
        if(isleap(cy))
            dpm[2]=29;
        else
            dpm[2]=28;
        while(cm<13) {
            while(cd<dpm[cm]) {
                cd++;
                fillin(cy,cm,cd);
                print();
            }
            cm++;
            cd=1;
            fillin(cy,cm,cd);
            print();
        }
        cy++;
        cm=cd=1;
        fillin(cy,cm,cd);
        print(); //为防止每月第一天的情况被遗漏,此处和84行都要调用print
    }
    return 0;
}

总结:对于日期相关的题目,在第三、第二层循环结束后要考虑是否执行第三层循环里面的操作。
试题H
思路: 一开始想用DFS,后来看了样例说明,发现直接暴力也可以,但会超时,只能过40%数据。
代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAX 100000
using namespace std;
int flag[100][100];
int fenzhi(char *s,int b,int e) {
    int cnt=0,i,j;
    for(i=b; i<e; i++) {
        for(j=b; j<i; j++) {
            if(s[j]==s[i])
                break;
        }
        if(j>=i)
            cnt++;
    }
    return cnt;
}

int main() {
    char c[MAX];
    long long int sum=0;
    gets(c);
    for(int i=0; i<=strlen(c); i++) {
        for(int j=i+1; j<=strlen(c); j++) {
//            printf("%d %d %d\n",i,j,fenzhi(c,i,j));
            sum+=fenzhi(c,i,j);
        }
    }
    printf("%I64d",sum);
    return 0;
}

会的就这么多。

2021.2.8更新
关于H题,想到一种更加高效的办法,可以过50%数据
那就是计算f值时,用一个数组标记某个字母是否出现过,如果未出现,则当前f值加1,并将该字母标记为出现过。
代码如下:

int fenzhi(char *s,int b,int e) {
    bool vis[26]; //判断每个字母是否出现过
    int cnt=0,i;
    memset(vis,false,sizeof(vis));
    for(i=b; i<e; i++) {
        if(!vis[s[i]-'a'])
            cnt++;
        vis[s[i]-'a']=true;
    }
    return cnt; 
}

总结一下,判断一个有限集合(如本题为小写字母集合)内某个元素是否出现过时,可设置标记数组。
再如:提取一系列非负整数(不大于某一特定值N)中仅出现1次的数,可设置长度为N+1的标记数组,只需一层循环,而不必使用两层。

更新
发现了一种新的做法:
用一个数组保存以每个字符开头的子串的f值,最后将它们相加即可
注意一下代码只能保存以i开头以最后一个字母结尾的子串的f值,所以cnt的位置应格外注意
可以通过60%数据,极限数据量约为29000
代码:

#include <cstdio>
#include <algorithm>
#include <string>
#include <iostream>
#define MAX_LEN 100001
using namespace std;
long long fz[MAX_LEN]; //记录以i起始至字符串结尾子串的f值
bool vis[27]; //记录每个字符的首次出现的位置 
int main(){
	long long cnt=0;
    string s;
	cin>>s;
	int len=s.length();
	cnt=len; //只有一个字符时
	fill(fz,fz+len,1);
	for(int i=0;i<len;i++){ //起始字符 
		fill(vis,vis+27,0);
		vis[s[i]-'a']=true;
		for(int j=i+1;j<len;j++){ //结尾字符 
			if(!vis[s[j]-'a']){
				fz[i]++;
				vis[s[j]-'a']=true;
			}
			cnt+=fz[i]; //这个要放在if外部 
		}
	}
	printf("%lld",cnt);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值