模拟算法题:不是用经典算法解出来的题目都可以称为模拟题。
目录
题目来源于蓝桥杯比赛真题
01 - 错误票据
题目详情:
某涉密单位下发了某种票据,并要在年终全部收回。
每张票据有唯一的ID号。
全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。
因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。
你的任务是通过编程,找出断号的ID和重号的ID。
假设断号不可能发生在最大和最小号。
输入格式
第一行包含整数 N,表示后面共有 N 行数据。
接下来 N 行,每行包含空格分开的若干个(不大于100个)正整数(不大于100000),每个整数代表一个ID号。
输出格式
要求程序输出1行,含两个整数 m,n用空格分隔。
其中,m表示断号ID,n表示重号ID。
数据范围
1≤N≤100
输入样例:
2 5 6 8 11 9 10 12 9
输出样例:
7 9
分析
方法1
开一个bool数组来记录每个数是否被用过
方法2
排序
方法1 - 代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010; //每个正整数不大于100000
bool st[N] = {0};
int a[N];
int m,n;
int main()
{
cin>>n; //n行
int flag = 0; //数组下标
while(n --)
{
int i;
while( cin>>i ){
//必须要在判断的前面
a[flag ++ ] = i;
//遇到换行符'\n'终止
char c = cin.get();
if(c == '\n') break;
}
}
//找重号
for(int i = 0; i < flag; i ++)
{
//如果原先已经出现过,表示是重号
if( st[a[i]] )
n = a[i];
//将出现过的数的状态设置为 true
st[a[i]] = true;
}
//找断号
int start = 0; //票号从一个随机的数开始
while(!st[start]) start ++; //找票据的最小值
for(int i = start; i < start + flag; i ++){
if( !st[i] ){
m = i;
break;
}
}
cout<<m<<" "<<n<<endl;
return 0;
}
方法2 - 代码
#include<cstring>
#include<sstream>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 10010; //每个正整数不大于100000
bool st[N] = {0};
int a[N];
int m,n;
int flag;
//方法2 -- 排序
int main()
{
int cnt;
cin>>cnt;
string line;
//用getline读,但是回车也会被读进去
getline(cin,line); //忽略掉第一行的回车
while(cnt--)
{
getline(cin,line);
stringstream ssin(line);
//初始完后可以从line中一个个读取字符
//像cin一样用ssin,读取字符的地方不一样
while(ssin >> a[flag]) flag++;
}
sort(a,a + flag);
//断号不会是最大或最小值
for(int i = 1; i < flag; i ++)
{
if(a[i] == a[i-1])
n = a[i];
else if(a[i] > a[i-1] + 1)
m = a[i] - 1;
}
cout<<m<<" "<<n;
return 0;
}
02 - 回文日期
枚举 + 模拟 组合版
题目详情
在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。
牛牛习惯用 88 位数字表示一个日期,其中,前 4 位代表年份,接下来 2 位代表月份,最后 2 位代表日期。
显然:一个日期只有一种表示方法,而两个不同的日期的表示方法不会相同。
牛牛认为,一个日期是回文的,当且仅当表示这个日期的 8 位数字是回文的。
现在,牛牛想知道:在他指定的两个日期之间(包含这两个日期本身),有多少个真实存在的日期是回文的。
一个 8 位数字是回文的,当且仅当对于所有的 i(1≤i≤8) 从左向右数的第 i 个数字和第 9−i 个数字(即从右向左数的第 ii 个数字)是相同的。
例如:
- 对于 2016 年 11 月 19 日,用 8 位数字 20161119 表示,它不是回文的。
- 对于 2010 年 11 月 2 日,用 8 位数字 20100102 表示,它是回文的。
- 对于 2010 年 10 月 2 日,用 8 位数字 20101002 表示,它不是回文的。
输入格式
输入包括两行,每行包括一个 8 位数字。
第一行表示牛牛指定的起始日期 date1,第二行表示牛牛指定的终止日期 date2。保证 date1 和 date2 都是真实存在的日期,且年份部分一定为 4 位数字,且首位数字不为 0。
保证 date1 一定不晚于 date2。
输出格式
输出共一行,包含一个整数,表示在 date1 和 date2 之间,有多少个日期是回文的。
输入样例:
20110101 20111231
输出样例:
1
分析
代码
#include<iostream>
#include<algorithm>
using namespace std;
int days[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
//判断日期是否合法
bool check_valid(int date)
{
//获取回文数的年月日
int year = date / 10000;
int month = (date % 10000) / 100;
int day = date % 100;
//判断月份是否合法
if(month == 0 || month > 12) return false;
//判断天数是否合法
if(day == 0 || day > days[month] && month != 2) return false;
//特判一下平年和闰年
if(month == 2){
int leap = year%4==0 && year%100!=0 || year%400 == 0;
if(day > 28 + leap) return false;
}
return true;
}
int main()
{
int date1,date2;
cin>>date1>>date2;
int res = 0;
//枚举回文数
for(int i = date1/10000 ;i < 10000;i ++)
{
int date = i,x = i;
while(x != 0){
date = date*10 + x%10;
x /= 10;
}
//判断是否在范围内 判断日期是否合法
if( date <= date2 && date >= date1 && check_valid(date) )
res++;
}
cout<<res;
return 0;
}
03 - 移动距离
知识点 - 向上取整、向下取整
头文件
#include<math.h> 或者 #include<cmath>
函数
函数名称 | 函数说明 |
---|---|
floor() | 不大于自变量的最大整数 |
ceil() | 不小于自变量的最大整数 |
round() | 四舍五入到最邻近的整数 |
fix() | 朝零方向取整 |
分析:
两种经典距离:
曼哈顿距离:
| x1 - x2 | + | y1 - y2 |
欧几里德距离:
√((x1 - x2)^2 + (y1 - y2)^2)
本题求的是曼哈顿距离
1)计算竖着走的步数
2)计算横着走的步数
代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> using namespace std; int w,m,n; int main() { scanf("%d%d%d",&w,&m,&n); int res = 0; //mx,nx为m、n的行数,竖着走的步数即为他们相减的绝对值 int mx = ceil(m*1.0 / w); int nx = ceil(n*1.0 / w); //计算竖着走的步数 res += fabs(nx - mx); //测试 // cout<<"竖着:"<<res<<" "; // cout<<"mx:"<<mx<<" "<<"nx:"<<nx<<endl; //计算横着要走的步数 //my,ny为m、n的列数,相减即为横着要走的步数 int my = 0, ny = 0; if(mx % 2 != 0) //当m在顺序行 { my = m % w; if(my == 0) my = w; } else if(mx % 2 == 0) //当m在逆序行 { if(m%w == 0) my = 1; else my = (w - m%w + 1); } if(nx % 2 != 0) //当n在顺序行 { ny = n % w; if(ny == 0) ny = w; } else if(nx % 2 == 0) //当n在逆序行 { if(n%w == 0) ny = 1; else ny = (w - n%w + 1); } // //测试 // cout<<"横着:"<<fabs(ny - my)<<" "; // cout<<"my:"<<my<<" "<<"ny:"<<ny<<endl; res += fabs(ny - my); cout<<res; return 0; }
上述方法用来求行号和列号有些许麻烦,我们不妨将每个楼号都减一,这样处理起来会简便许多,少了些特判
代码:
//2022.6.6 #include<iostream> #include<algorithm> #include<cstdio> #include<cmath> using namespace std; int main() { int w,m,n; cin>>w>>m>>n; m --, n --; int mx,my,nx,ny; mx = m/w, nx = n/w; my = m%w, ny = n%w; if(mx % 2 != 0) my = w - 1 - my; if(nx % 2 != 0) ny = w - 1 - ny; cout<<abs(mx - nx) + abs(my - ny)<<endl; return 0; }
04 - 日期问题
题意:
输入A,B,C三个两位的数字,代表的意义可能是年/月/日或月/日/年或日/月/年,其中年是年份的尾数,找出可能代表的合法日期,输出在1960年1月1日至2059年12月31日之间的日期
分析:
枚举所有日期:1960.1.1 ~ 2059.12.31
19600101 ~ 20591231
判断是否为合法日期
判断是否是给定的表示
代码:
#include<cstdio> #include<iostream> using namespace std; int days[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; bool check_valid(int year,int month,int day) { //判断月份是否合法 if(month == 0 || month >12) return false; //判断一下天数是否合法 if(day == 0 || month!=2 && day>days[month]) return false; //特判一下平年和闰年 if(month == 2) { int leap = (year % 4 ==0 && year % 100 !=0 || year % 400 ==0); if(day > 28 + leap) return false; } return true; } int main() { int a,b,c; scanf("%d/%d/%d",&a,&b,&c); for(int date = 19600101; date < 20591231; date ++) { int year = date / 10000; int month = date % 10000 / 100; int day = date % 100; //判断日期是否合法 if(check_valid(year,month,day)) { //判断是否是给定的日期 //三种情况 if(year % 100 == a && month == b && day == c || //年/月/日 year % 100 == c && month == a && day == b || //月/日/年 year % 100 == c && month == b && day == a) //日/月/年 { printf("%d-%02d-%02d\n",year,month,day); } } } return 0; }
知识点 - 结构体的使用
类型声明
struct stu { char name[10]; short age; char tele[20]; char sex[5]; }s1,s2,s3; int main() { struct stu s4; }
struct 结构体关键字
stu 结构体标签
struct stu 结构体类型
s1,s2,s3 是三个全局的结构体变量
s4 是一个局部变量
用typedef将结构体改名为STU
typedef strustuct { char name[10]; short age; char tele[20]; char sex[5]; }STU; int main() { STU s5; }
注意: STU是类型,stu、STU可以同名 s1,s2,s3,s4,s5都是变量
//结构体变量的定义和初始化 struct Point { char name[10] int x; int y; }p1; //声明类型的同时定义变量p1 struct Point p2; //定义一个新变量p2 p2={"张三",100,90}; //结构体传参: typedef struct stu { char name[10]; short age; char tele[20]; char sex[5]; }STU; void print1(STU ps) { printf("name:%s\n", ps.name); printf("name:%d\n", ps.age); printf("tele:%s\n", ps.sex); printf("sex :%s\n", ps.sex); } void print2(STU* ps) { printf("name:%s\n", ps->name); printf("name:%d\n", ps->age); printf("tele:%s\n", ps->tele); printf("sex :%s\n", ps->sex); } int main() { STU s = { "张三",18,"1111","男" }; //打印结构体数据 print1(s); print2(&s); }
-
print1和print2哪种打印的方式好一些?
答案:首选print2
原因:函数传参的时候,参数是需要压栈的,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
结论:结构体传参时,要传结构体的地址。
05 - 航班时间
题目详情:
小 h 前往美国参加了蓝桥杯国际赛。
小 hh 的女朋友发现小 h 上午十点出发,上午十二点到达美国,于是感叹到“现在飞机飞得真快,两小时就能到美国了”。
小 h 对超音速飞行感到十分恐惧。
仔细观察后发现飞机的起降时间都是当地时间。
由于北京和美国东部有 12 小时时差,故飞机总共需要 14 小时的飞行时间。
不久后小 h 的女朋友去中东交换。
小 h 并不知道中东与北京的时差。
但是小 h 得到了女朋友来回航班的起降时间。
小 h 想知道女朋友的航班飞行时间是多少。
对于一个可能跨时区的航班,给定来回程的起降时间。
假设飞机来回飞行时间相同,求飞机的飞行时间。
输入格式
一个输入包含多组数据。
输入第一行为一个正整数 T,表示输入数据组数。
每组数据包含两行,第一行为去程的起降时间,第二行为回程的起降时间。
起降时间的格式如下:
h1:m1:s1 h2:m2:s2
h1:m1:s1 h3:m3:s3 (+1)
h1:m1:s1 h4:m4:s4 (+2)
第一种格式表示该航班在当地时间h1时m1分s1秒起飞,在当地时间当日h2时m2分s2秒降落。
第二种格式表示该航班在当地时间h1时m1分s1秒起飞,在当地时间次日h2时m2分s2秒降落。
第三种格式表示该航班在当地时间h1时m1分s1秒起飞,在当地时间第三日h2时m2分s2秒降落。
输出格式
对于每一组数据输出一行一个时间hh:mm:ss,表示飞行时间为hh小时mm分ss秒。
注意,当时间为一位数时,要补齐前导零,如三小时四分五秒应写为03:04:05。
数据范围
保证输入时间合法(0≤h≤23,0≤m,s≤59),飞行时间不超过24小时。
输入样例:
3 17:48:19 21:57:24 11:05:18 15:14:23 17:21:07 00:31:46 (+1) 23:02:41 16:13:20 (+1) 10:19:19 20:41:24 22:19:04 16:41:09 (+1)输出样例:
04:09:05 12:10:39 14:22:05
知识点 - sscanf、c_str、getline
分析:
分析题意:
给定一趟航班的来回出发、到达的本地时间,但不知道两地的时差是多少,想知道真实的飞行时间
去:出发时间(t1) 到达时间(t2)
回:出发时间(t3) 到达时间(t4)
举例:
北京 ==> 中东
往西飞,飞行的时长是
相差的时间 + 时差
中东 ==> 北京
往东飞,飞行的时长是
相差的时间 - 时差
那么
相差的时间1 + 时差 = 相差的时间2 - 时差
即(t2 - t1) - (t4 - t3) = 2时差
解题步骤:
将所有的时间转化距离当天 00:00:00 的秒,方便计算时间的差值
处理时间 time ,将秒数变为时分秒的形式
hour = time / 3600
minute = time % 3600 / 60
second = time % 60
注意:此题需要掌握输入输出的技巧,还有一些字符串函数
代码(sscanf版本):
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int get_second(int h,int m,int s) { return h * 3600 + m * 60 + s; } int get_time() { string line; getline(cin,line); //将字符串格式全都转化为一样的 if(line.back() != ')') line += " (+0)"; int h1,m1,s1,h2,m2,s2,d; //利用 c_str() 函数将string对象转换为 C 中的字符串样式 sscanf(line.c_str(), "%d:%d:%d %d:%d:%d (+%d)",&h1,&m1,&s1,&h2,&m2,&s2,&d); return get_second(h2,m2,s2) - get_second(h1,m1,s1) + d * 24 * 3600; } int main() { int n; scanf("%d",&n); //忽略第一行的回车 getchar()也行 string line; getline(cin,line); //getchar(); while(n --) { int time = (get_time() + get_time()) / 2; int hour = time / 3600; int minute = time % 3600 /60; int second = time % 60; printf("%02d:%02d:%02d\n",hour,minute,second); } return 0; }
代码(scanf版本):
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; //scanf输入版本 int get_seconds(int h,int m,int s) { return h*3600 + m*60 + s ; } int get_time() { int h1,m1,s1,h2,m2,s2,d=0; scanf("%d:%d:%d %d:%d:%d (+%d)",&h1,&m1,&s1,&h2,&m2,&s2,&d); return get_seconds(h2,m2,s2) - get_seconds(h1,m1,s1) + d*24*3600; } int main() { int n; scanf("%d",&n); while(n -- ) { int time = (get_time() + get_time()) / 2; int hour = time / 3600; int minute = time / 60 % 60; int second = time % 60; printf("%02d:%02d:%02d\n",hour,minute,second); } return 0; }
06 - 外卖店优先级
题目详情:
“饱了么”外卖系统中维护着 N 家外卖店,编号 1∼N。
每家外卖店都有一个优先级,初始时 (0 时刻) 优先级都为 0。
每经过 1 个时间单位,如果外卖店没有订单,则优先级会减少 1,最低减到 0;而如果外卖店有订单,则优先级不减反加,每有一单优先级加 2。
如果某家外卖店某时刻优先级大于 5,则会被系统加入优先缓存中;如果优先级小于等于 3,则会被清除出优先缓存。
给定 T 时刻以内的 M 条订单信息,请你计算 T 时刻时有多少外卖店在优先缓存中。
输入格式
第一行包含 3 个整数 N,M,T。
以下 M 行每行包含两个整数 ts 和 id,表示 ts 时刻编号 id 的外卖店收到一个订单。
输出格式
输出一个整数代表答案。
数据范围
1≤N,M,T≤10^5, 1≤ts≤T, 1≤id≤N,
输入样例:
2 6 6 1 1 5 2 3 1 6 2 2 1 6 2输出样例:
1样例解释
6 时刻时,1 号店优先级降到 3,被移除出优先缓存;2 号店优先级升到 6,加入优先缓存。
所以是有 1 家店 (2 号) 在优先缓存中。
题意分析:
输入:
N: 维护的店子的数量
M: 平台在T时刻前收到的所有订单数量,订单信息包括下单单元时刻和下单店家的id号
T : T时刻之前收到的订单数量
输出:T时刻时优先缓存的店子的数量
代码(暴力写法)
#include<cstdio> #include<cmath> #include<algorithm> #include<iostream> using namespace std; const int N = 20000; int a[N][N]; //i 时刻 j 号店铺的订单情况,没有为 0 bool st[N]; //是否在优先级缓存中 int n,m,t; //三个输入 int score[N]; //店铺的优先级 //暴力写法 - 可以过掉一部分 int main() { cin>>n>>m>>t; //输入m个订单信息 for(int i = 0;i < m; i ++) { int j,k; cin>>j>>k; a[j][k]++; } int res = 0; for(int i = 1;i < t + 1; i ++) { for(int j = 1; j < n + 1; j ++) { if(a[i][j] > 0){ score[j] += a[i][j] * 2; }else{ if(score[j]) score[j] -= 1; } if(score[j] > 5) st[j] = true; if(score[j] <= 3) st[j] = false; // cout<<score[j]<<" "; } // cout<<endl; } for(int i = 1;i <= n;i ++) res+=st[i]; cout<<res; return 0; }
优化版
变量解释:
score[i]:表示第i个店铺当前的优先级t[]
last[i] :表示第i个店铺上一次有订单的时刻
st[i] :表示第i个店铺当前是否处于优先级中
思路
假设某店铺拿到订单的时刻分别为t1, t2, t3, t4(假设已经按照非递减序排序),问第t4时刻店铺的优先级(根据题目t4>=t1,t2,t3) 解:
-
总共需要计算2个部分:
-
第一部分:1 ~ t1-1, t1+1 ~ t2-1, t2+1 ~ t3-1, t3+1 ~ t4-1, t4+1 ~ T,这几个部分是没有订单的时刻,需要减去这些个时刻
-
第二部分:t1, t2, t3,t4 这几个时刻是接到接到订单的时刻,这几个时刻分别加2
-
-
需要注意的是,如果同一时间有多个订单,假如上述的非递减序列中,t2 == t3,思路如下:
-
第一部分:1 ~ t1-1, t1+1 ~ t2-1, t3+1 ~ t4-1, t4+1 ~ T,这几个部分是没有订单的时刻,需要减去这些个时刻
-
第二部分:t1, t2, t4 这几个时刻是接到接到订单的时刻,这几个时刻分别加上该时刻订单数量cnt*2(cnt[t1] = 1, cnt[t2] = 2, cnt[t4] = 1)
-
-
具体步骤:
-
输入的m个订单信息,排序(时间 t 为第一优先级,订单 id 为第二优先级)
-
for 遍历订单信息(记得此时订单是按照时间顺序排的)。
-
假设当前订单为第 i 个,循环判断后面有没有相同的订单(即 ts 和 id 相等)。(有的话一定连续)
-
当到第 j 个时订单不相同,此时相同订单的数量为
cnt=j−i
-
下一次for循环从 j 处开始遍历
-
记录此时的t和id, 计算 该id 的优先权,有两部分
-
第一部分,是上一个拿到订单的时间last[id]和t之间,中间没订单所以要−1,没订单的数量是
t−last[i]−1
(比如第3和第6时刻都有订单,没有订单的时候就是4,5) -
计算优先权,如果为负值更新为0。如果小于等于3,更新优先缓存 st[id]=falsest
-
第二部分,是此时,t时刻拿到订单,并且拿到的数量为cnt,要加上2∗cnt
-
计算优先权,如果大于5,更新优先缓存st[id]=true
-
-
解释上面那个,因为此时这几个相同的订单都计算过了不需要再计算了,所以下一次循环要从j开始
-
循环最后,上一次拿到订单的时间last[id]更新为t
-
如果最后一个订单时刻为T,则没问题。如果不是T,那么最后一个拿到订单时刻到T时刻的这部分减法需要手动计算,即减去最后一个订单时刻与T的差值。换而言之,如果上一个拿到订单的时间last[id]小于T,则 优先权score 减去T−last[id]。注意这里不减1,因为T时刻也没订单。如果小于等于3,更新优先缓存
-
代码(优化版)
#include<cstdio> #include<cmath> #include<algorithm> #include<iostream> #define ts first #define id second using namespace std; typedef pair<int,int> PII; const int N = 100010; bool st[N]; //是否在优先级缓存中 int n,m,T; //三个输入 int last[N]; int score[N]; //店铺的优先级 PII order[N]; int main() { cin>>n>>m>>T; for(int i = 0; i < m; i ++) scanf("%d%d",&order[i].ts,&order[i].id); sort(order,order + m); //只处理有订单信息的店铺 //没有订单信息的店铺的优先级一直为零 for(int i = 0; i < m; ) { int j = i; while(j < m && order[j] == order[i]) j++; int t = order[i].ts,id = order[i].id; int cnt = j - i; i = j; //第一部分 - 处理 t 之前的信息 score[id] -= (t - last[id] - 1); if(score[id] < 0) score[id] = 0; if(score[id] <= 3) st[id] = false; //第二部分 - 处理 t 时刻的信息 score[id] += cnt*2; if(score[id] > 5) st[id] = true; last[id] = t; } for(int i = 1; i <= n; i ++) { if(last[i] < T) { score[i] -= T - last[i]; if(score[i] <= 3) st[i] = false; } } int res = 0; for(int i = 0; i < m; i ++) res += st[i]; cout<<res; return 0; }