试题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了,则说明已经凑到最大数字了
注意
- 该代码有个漏洞,当十位、百位、千位等为0时,0会被减多,导致程序返回的是错误答案
- 结束的标志为卡片不能构成数字,此时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;
}
本期总结
- 内存进制单位 MB-KB-B-b
- 对于拼凑数字类型的题,通过while()循环取出末位数较为简便
- 最短路径问题(动态规划),采用动态规划数组迭代寻找出最短路径和
- 时间问题,根据题目要求进行时间的进制转换即可,注意格式化输出(%02d表示输出宽度为2位,不足两位的前面补0)
- 排序问题,暴力解决可直接使用sort()进行排序,但会超时,注意sort()是左闭右开的,默认为升序,降序在最后位置加上greater<int>()