【C++】蓝桥杯第十二届省赛(解题过程详解)

试题A:空间

小蓝准备用 256MB 的内存空间开一个数组,数组的每个元素都是 32 位二进制整数,如果不考虑程序占用的空间和维护内存需要的辅助空间,请问256MB 的空间可以存储多少个 32 位二进制整数?

【答案提交】

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

方法:借助计算器

根据换算关系:

1MB=1024KB 1KB=1024B 1B=8b

1B代表字节,b代表位,即32位4个字节

即答案为:((256*1024)*1024)/ 4== 67108864


试题B:卡片

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

小蓝有很多数字卡片,每张卡片上都是数字 00 到 99。

小蓝准备用这些卡片来拼一些数,他想从 11 开始拼出正整数,每拼一个,就保存起来,卡片就不能用来拼其它数了。

小蓝想知道自己能从 11 拼到多少。

例如,当小蓝有 3030 张卡片,其中 00 到 99 各 33 张,则小蓝可以拼出 11 到 1010,

但是拼 1111 时卡片 11 已经只有一张了,不够拼出 1111。

现在小蓝手里有 00 到 99 的卡片各 20212021 张,共 2021020210 张,请问小蓝可以从 11 拼到多少?

提示:建议使用计算机编程解决问题。

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路

对0~9的数字建立哈希表,初始化val=2021

分别取出个位、十位、百位、千位等,对相应的hash作-1操作

如果hash表中有一个key值先减到0了,则说明已经凑到最大数字了

注意

  1. 该代码有个漏洞,当十位、百位、千位等为0时,0会被减多,导致程序返回的是错误答案
  2. 结束的标志为卡片不能构成数字,此时sum已经+1 ,输出时要把1减去

完整代码如下(ACM模式)(有漏洞)

#include<iostream>
#include<string.h>//使用memset() 
using namespace std;

int main(){
	int hash[11]={2021,2021,2021,2021,2021,2021,2021,2021,2021,2021};
	//memset(hash,2021,sizeof(hash));	//memset()最好只初始化0和1 
	int i=1;
	int sum=0;
	
	//法一 
	bool flag=false;//循环结束标志 
	while(flag!=true){
		if(i==9){
			i=0;
		}
		int a=sum%10;//个位 
		int b=sum/10%10;//十位 
		int c=sum/100%10;//百位 
		int d=sum/1000%10;//千位 
		int e=sum/10000%10;//万位
		//有漏洞,默认构成四位数,不足4位的前面的0也被减去,因此应该用while循环来减 
		hash[a]--;
		hash[b]--;
		hash[c]--;
		hash[d]--;
		hash[e]--;
		if(hash[a]==0){
			flag=true;
		}
		if(hash[b]==0){
			flag=true;
		}
		if(hash[c]==0){
			flag=true;
		}
		if(hash[d]==0){
			flag=true;
		}
		if(hash[e]==0){
			flag=true;
		}
		sum++;
	}
	cout<<sum-1<<endl;//结束的标志为卡片不能构成数字,此时sum已经+1 
}

对上面代码的改进

思路

设立一个临时变量x用于存放当前的数,使用while()循环每次取出x的最后一位,减去hash表对应的值,

当hash表中有减到0时,退出程序(eit(0))

改进版——完整代码如下(ACM模式)

#include<iostream>
#include<string.h>//使用memset() 
using namespace std;

int main(){
	int hash[11]={2021,2021,2021,2021,2021,2021,2021,2021,2021,2021};
	//	memset(hash,20,sizeof(hash));	//memset()是对字节进行初始化,因此对于int型数组只能初始化为0或-1 
	int i=1;

	//法二:对法一的改进 
	for(int i=1;;i++){
		int x=i;
		while(x){
			if(hash[x%10]==0){
				cout<<i-1<<endl;
				exit(0);//在子程序中用来终结程序
			}
			hash[x%10]--;
			x=x/10;
		}
	}

}

函数初始化memset()https://www.yuque.com/bairimengxiangxia/sft1wu/urbbcftex1lxmd7z

头文件<string.h>

memset()对int型数组只初始化0和-1

meset常见错误memset函数及其用法(最全详解)_m0_51610850的博客-CSDN博客

第一:memset函数按字节对内存块进行初始化,所以不能用它将int数组初始化为0和-1之外的其他值(除非该值高字节和低字节相同)。

第二:memset(void *s, int ch,size_t n);中ch实际范围应该在0~~255,因为该函数只能取ch的后八位赋值给你所输入的范围的每个字节,比如int a[5]赋值memset(a,-1,sizeof(int )*5)与memset(a,511,sizeof(int )*5) 所赋值的结果是一样的都为-1;因为-1的二进制码为(11111111 11111111 11111111 11111111)而511的二进制码为(00000000 00000000 00000001 11111111)后八位都为(11111111),所以数组中每个字节,如a[0]含四个字节都被赋值为(11111111),其结果为a[0](11111111 11111111 11111111 11111111),即a[0]=-1,因此无论ch多大只有后八位二进制有效,而后八位二进制的范围在(0~255)中改。而对字符数组操作时则取后八位赋值给字符数组,其八位值作为ASCII码


 

试题E:路径用户登录

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

小蓝学习了最短路径之后特别高兴,他定义了一个特别的图,希望找到图 中的最短路径。

小蓝的图由 2021 个结点组成,依次编号 1 至 2021。

对于两个不同的结点 a, b,如果 a 和 b 的差的绝对值大于 21,则两个结点 之间没有边相连;如果 a 和 b 的差的绝对值小于等于 21,则两个点之间有一条 长度为 a 和 b 的最小公倍数的无向边相连。

例如:结点 1 和结点 23 之间没有边相连;结点 3 和结点 24 之间有一条无 向边,长度为 24;结点 15 和结点 25 之间有一条无向边,长度为 75。

请计算,结点 1 和结点 2021 之间的最短路径长度是多少。

提示:建议使用计算机编程解决问题。

思路(动态规划)

1.确定动态规划数组以及下标的含义

创建数组d[i],表示结点i距离结点1的最短路径长度

2.确定递推公式

内层循环找出与结点j距离<=21的最短距离,并与上次的结果累加得到距离结点1的最短路径

dd[j]=min(d[j],lcm(i,j)+d[i]);

3.动态规划数组如何初始化

根据题意得

d[0]=0; 结点0不存在,所以到结点1的最短路径为0

d[1]=0; 结点1到结点1的最短路径为0

4.确定遍历顺序

d的值依赖前次获得的值,因此是从前往后遍历

5.举例推导dp数组

完整代码如下(ACM模式)

#include <iostream>
#include<string.h>
//#define INF 0x3f3f3f3f	//ACM中常用的无穷大常量——0x3f3f3f3f
using namespace std;

int d[2025];//d[i]表示节点i与1的最短路径为d[i] 

//求最大公约数 
int gcd(int a,int b){
	if(a%b==0){
		return b;
	}else{
		return gcd(b,a%b);
	}

}

//求最小公倍数 
int lcm(int a,int b){
	int g=gcd(a,b);
	return (a*b)/g;
}

int main()
{
  // 请在此输入您的代码
  memset(d,0x3f,sizeof(d));//为避免繁琐,也使用0x3f作为无穷大 
  
  //初始化 
  d[0]=0;//结点0不存在,所以到结点1的最短路径为0 
  d[1]=0;//结点1到结点1的最短路径为0 
  int n=2021; 
  for(int i=1;i<=n;i++){
  	for(int j=i+1;j<=n && j-i<=21;j++){//内层循环找出与结点j距离<=21的最短距离,并与上次的结果累加得到距离结点1的最短路径 
  		d[j]=min(d[j],lcm(i,j)+d[i]);
	  }
  }
  cout<<d[2021]<<endl;
  return 0;
}

扩展

常用无穷大常量——0x3f3f3f3f0x3f3f3f3f是什么意思???_我对算法一无所知的博客-CSDN博客

0x3f3f3f3f的十进制是1061109567,是10^9级别的,而一般场合下的数据都是小于10^9的,所以它可以作为无穷大使用而不致出现数据大于无穷大的情形

为了避免加法算术上溢出或者繁琐的判断,我们经常用 memset(a, 0x3f, sizeof(a)) 给数组赋 0x3f3f3f3f的值来代替。

辗转相除法得最大公约数和最小公倍数c语言详细解答辗转相除法求两个数的最小公倍数_辗转相除法求最小公倍数_专科在努力!的博客-CSDN博客

https://www.yuque.com/bairimengxiangxia/sft1wu/hwhvwkv5ivczi95i

最大公约数

辗转相除法是用一个大的数除以一个小的数,如果有余数,就用被除数➗余数,如果还有余数就继续用(上一个公式的) 被除数➗余数,直到余数为0时停止。当余数为0的时候,除数➗被除数=商……余数(为0) 此时的被除数就是最大公约数(最大公因数)。

最小公倍数

最小公倍数就等于这两个数的乘积数以最大公约数

例如求a,b的最小公倍数

最小公倍数 = a*b /(a与b的最大公约数)


试题F:时间显示用户登录

小蓝要和朋友合作开发一个时间显示的网站。

在服务器上,朋友已经获取了当前的时间,用一个整数表示,值为从 19701970 年 11 月 11 日 00:00:0000:00:00 到当前时刻经过的毫秒数。

现在,小蓝要在客户端显示出这个时间。小蓝不用显示出年月日,只需要显示出时分秒即可,毫秒也不用显示,直接舍去即可。

给定一个用整数表示的时间,请将这个时间对应的时分秒输出。

输入描述

输入一行包含一个整数,表示时间。

输出描述

输出时分秒表示的当前时间,格式形如 HH:MM:SS,其中 HH 表示时,值为 00 到 2323,MM 表示分,值为 00 到 5959,SS 表示秒,值为 00 到 5959。时、分、秒 不足两位时补前导 00。

输入输出样例

示例 1

输入

46800999

输出

13:00:00

示例

输入

1618708103123

输出

01:08:23

思路

将毫秒转为秒(1s=1000ms)

分别除以时间的进制获得时分秒

完整代码如下(ACM模式)

#include <iostream>
#include<cstdio>
using namespace std;
int main()
{
  // 请在此输入您的代码
  long long n=0;
  cin>>n;
  n=n/1000;//1s=1000ms	将n换算为秒 
  long long s=n/1%60;//获得秒数 
  long long m=n/60%60;//获得分数 
  long long h=n/3600%24;//获得时数 
  printf("%02d:%02d:%02d",h,m,s);
  return 0;
}

时间进制转换

与十进制获得个位十位百位类似

获得时间的秒位分位时位的进制如下

s=t/1%60

m=t/60%60

h=t/3600%24


试题I 双向排序

给定序列(a[1], a[2], … , a[n]) = (1, 2, … , n),即a[i] = i。

小蓝将对这个序列进行m次操作,每次可能是将a[1], a[2], … a[qi] 降序排列,或者将a[qi], a[qi+1], …a[n] 升序排列。 请求出操作完成后的序列。

输入格式:

输入的第一行包含两个整数n, m,分别表示序列的长度和操作次数。 接下来m行描述对序列的操作,其中第i行包含两个整数pi, qi

表示操作类型和参数。

当pi = 0 时,表示将a[1], a[2], … a[qi] 降序排列;

当pi = 1

时,表示将a[qi], a[qi+1], … , a[n] 升序排列。

对于30%的评测用例,n,m ≤ 1000;

对于60% 的评测用例,n,m ≤ 5000;

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

示例

输入

3 3 0 3 1 2 0 2

输出

3 1 2

样例说明

原数列为 (1,2,3)(1,2,3)。

第 11 步后为 (3,2,1)(3,2,1)。

第 22 步后为 (3,1,2)(3,1,2)。

第 33 步后为 (3,1,2)(3,1,2)。与第 22 步操作后相同,因为前两个数已经是降序了。

思路

暴力——sort()函数

如果是降序,则数组[0,0+b)范围降序排列

如果是升序,则数组[0+b-1,n)范围升序排列

注意

sort()函数头文件在<algorithm>中,且默认升序,降序格式为sort(起点,终点,greater<int>())

sort()是左闭右开的!!!

由于测试用例<=100000,则使用sort()会超时,只能过60%测试案例

暴力sort()——完整代码如下(ACM模式)

#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
  // 请在此输入您的代码
  vector<int> vec;
  int n,m;
  cin>>n>>m;
  for(int i=1;i<=n;i++){
  	vec.push_back(i);
  } 
  int a=0,b=0;
  while(m--){
    cin>>a>>b;
    if(a==0){
      sort(vec.begin(),vec.begin()+b,greater<int>());//对[0,0+b)降序
    }
    if(a==1){
      sort(vec.begin()+b-1,vec.end());//对[0+b-1,n)升序 
    }
  }

  for(int i=0;i<vec.size();i++){
    cout<<vec[i]<<" ";
  }
  return 0;
}

本期总结

  1. 内存进制单位 MB-KB-B-b
  2. 对于拼凑数字类型的题,通过while()循环取出末位数较为简便
  3. 最短路径问题(动态规划),采用动态规划数组迭代寻找出最短路径和
  4. 时间问题,根据题目要求进行时间的进制转换即可,注意格式化输出(%02d表示输出宽度为2位,不足两位的前面补0)
  5. 排序问题,暴力解决可直接使用sort()进行排序,但会超时,注意sort()是左闭右开的,默认为升序,降序在最后位置加上greater<int>()

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值