计算机考研机试攻略 - 高分篇

计算机考研机试攻略 - 高分篇_第1张图片


目录

第一章 从零开始

1.1机试分析

1.2 IDE的选择与评测结果

1.3 DreamJudge的使用

1.4输入输出技巧

1.5头文件技巧

1.6数组使用技巧

1.7审时度势 — 复杂度与是否可做

1.8 C++ STL的使用

1.9多组输入的问题

第二章 入门经典

2.1 简单模拟

2.2 进制转换类问题

2.3 排版类问题

2.4 日期类问题

2.5 字符串类问题

2.6 排序类问题

2.7 查找类问题

2.8 贪心类问题

2.9 链表类问题

第三章 数学

3.1 同模余定理

3.2 最大公约数(GCD)

3.3 最小公倍数(LCM)

3.4 斐波那契数列

3.5 素数判定

3.6 素数筛选

3.7 分解素因数

3.8 二分快速幂

3.9 常见数学公式总结

3.10 规律神器OEIS

第四章 高精度问题

4.1 Python解法

4.2 Java解法

4.3 C/C++解法

第五章 数据结构

5.1 栈的应用

5.2 哈夫曼树

5.3 二叉树

5.4 二叉排序树

5.5 hash算法

5.6 前缀树

第六章 搜索

6.1 暴力枚举

6.2 广度优先搜索(BFS)

6.3 递归及其应用

6.4 深度优先搜索(DFS)

6.5 搜索剪枝技巧

6.6终极偏分技巧

第七章 图论

7.1 理论基础

7.2 图的存储

7.3 并查集

7.4 最小生成树问题

7.5 最短路径问题

7.6 拓扑排序

7.7 打印路径类问题

第八章 动态规划

8.1 递推求解

8.2最大子段和

8.3 最长上升子序列(LIS)

8.4 最长公共子序列(LCS)

8.5 背包类问题

8.6 记忆化搜索

8.7 字符串相关的动态规划

 


 

 

第一章 从零开始

 

1.1机试分析

首先我们来看一下机试是怎样的一种考核模式

全国所有院校的机试都大同小异,大部分有自己OJ(Online Judge也就是在线代码测评平台)的学校都会采用OJ上做题的方式来进行考核。这种考核方式的好处是公开透明,机器进行判题并给分,就像N诺的DreamJudge一样。没有OJ的学校只能人工进行判题,人工判题的话,一方面是主观性比较强,可能还会对其他方面进行考量,这个就需要自己去了解了。总的来说,不论是OJ判题还是人工判题,代码都要能通过测试用例才能得到分数。

 

针对机试我们应该怎么去训练提升自己呢

首先,一定要做题,在N诺上做题,不要自己埋头看书,在不断做题的过程中才能提升自己。如果非要用一个量化的标准来衡量的话,至少要在N诺上做够100题(也就是达到砖石II以上段位),才能保证你机试达到自己满意的成绩。题量是很关键的,看懂的题再多,都不如自己实际敲代码去解决问题更稳妥。

 

解题速度也是很重要的

其实解题速度和题量是正相关的,相信题量足够的同学,解题的速度都不会太慢。机试一般2-3个小时左右,要解决5-8道题。平均下来一道题最多半个小时,从读题、分析题意、思考解法、敲代码、调试、测试数据到最后提交这整个流程下来,如果你平时训练的少,读题慢、理解题意慢、思考解法慢、敲代码慢、调试慢,这样一算下来,一道简单的题都可能要一个小时才能写出来,说不定题目还有坑点,再慢慢调试,基本上就凉了。

 

 

 

多打比赛也是很重要的

很多同学平时做的题很多,解题速度也挺快,但是一到比赛或者考试的时候就会卡题,在压力的情况下发挥失常比比皆是,所以平时就要锻炼自己的抗压能力。

N诺上除了每个周定期举办的小白赛,还特别为大家准备了考研机试冲刺八套卷

在本书的最后,你可以看到关于考研机试冲刺八套卷的详细信息。通过这八套卷的练习,相信会让你的水平产生一个脱胎换骨的变化。

 

 

准备好模板是至关重要的

一般来说,机试都可以带书和纸质资料进入考场。所以提前把那些函数的用法和算法的模板准备好是很重要的,一方面是增加自己的信心,万一没记住还可以翻开来看一下。另外说不定考到原题或者类似的题,就可以直接秒杀了。

 

 

特别提醒:本书默认读者是会C语言的基本语法的,比如if语句、for语句等等。

 

 

 

C语言基本语法还未掌握的同学建议学习N诺的C语言快速入门课程(3天学会C语言不是梦)

 

地址:http://www.noobdream.com/Major/majorinfo/1/

 

 

 

 

 

 

1.2 IDE的选择与评测结果

建议选择CodeBlocks作为平时敲代码练习的IDE

下载地址:http://www.noobdream.com/Major/article/1/

 

常见做题结果反馈

Accepted:答案正确,恭喜你正确通过了这道题目。

Wrong Answer: 答案错误,出现这个错误的原因一般是你的程序实现或思路出现了问题,或者数据范围边界没有考虑到。

Runtime Error:运行时错误,出现这个错误的原因一般是数组越界或者递归过深导致栈溢出。

Presentation Error:输出格式错误 ,出现这个错误的原因一般是末尾多了或少了空格,多了或少了换行

Time Limit Exceeded:程序运行超时,出现这个错误的原因一般是你的算法不够优秀,导致程序运行时间过长。

Memory Limit Exceeded:运行内存超限,出现这个错误的原因一般是你的程序申请太大了空间,超过了题目规定的空间大小。

Compile Error:编译错误,这个不用说了吧,就是你的代码存在语法错误,检查一下是不是选择错误的语言提交了。

Output Limit Exceeded:输出超限,程序输出过多的内容,一般是循环出了问题导致多次输出或者是调试信息忘记删除了。

Submitting:提交中,请等待题目结果的返回,由于判题机有性能差异,所以返回结果的速度也不一样,N诺上做题一般瞬间就能出结果。

 

以上几种结果就是评判系统可能会返回的几种常见结果。若返回Accept,那么你就可以拿到该题所有分数,如果返回其他结果,则要看你报考学校的考试规则,是根据通过测试点的百分比给分还是只要不是AC就得0分。

 

 

1.3 DreamJudge的使用

DreamJudge是一个在线代码测评的平台,可以很方便的检验自身的学习情使用DreamJudge的方法很简单,通过百度搜索N诺或者在浏览器中输入网址www.noobdream.com进入N诺然后点击网站导航上方的DreamJudge就可以进去啦。

 

做题页面如下:

计算机考研机试攻略 - 高分篇_第2张图片

 

首先要登录我们的N诺账号,然后开始做题。如果没有账号,右上角点击注册,然后注册一个账号就可以了。

然后将代码粘贴到右边的输入框里,在上面选择使用哪种语言提交,C/C++/Java/Python,建议选择C++提交,因为C++可以编译C语言代码。我们一般写代码为了方便,都会使用一点C++的特性来帮助我们快速解决一道题目。如果代码里含有C++的特性却选择了C语言提交的话,会返回编译错误的提示信息。

 

1.4输入输出技巧

输入int型变量 scanf("%d", &x);

输入double型变量 scanf("%lf", &x); 不用float直接double

输入char类型变量 scanf("%c", &x);

输入字符串数组 scanf("%s", s);

输出与输入表示方式一致

printf("%s\n", s);

 

scanf输入解析

输入日期 2019-10-21

  1. int year, month, day;  
  2. scanf("%d-%d-%d", &year, &month, &day);  
  3. printf("%d %d %d\n", year, month, day);  

这样可以直接解析出来

 

输入时间 18:21:30

  1. int hour, minute, second;  
  2. scanf("%d:%d:%d", &hour, &minute, &second);  
  3. printf("%d %d %d\n", hour, minute, second);  

 

scanf和gets

输入一行字符串带空格的话,使用gets,scanf遇到空格会自动结束

  1. char s[105];  
  2. gets(s);//例如输入how are you?  
  3. printf("%s\n", s);  

 

getchar和putchar

读入单个字符和输出单个字符,一般在scanf和gets中间使用getchar用于消除回车’\n’的影响

 

 

 

输出进制转换

  1. int a = 10;  
  2. printf("%x\n", a);//小写十六进制输出 答案a  
  3. printf("%X\n", a);//大写十六进制输出 答案A  
  4. printf("%o\n", a);//八进制输出  答案12  

 

输出增加前置0

  1. int a = 5;  
  2. printf("%02d\n", a);//其中2代表宽度 不足的地方用0补充  
  3. 输出结果05  
  4. printf("%04d\n", a);  
  5. 输出结果0005  

 

输出保留小数

  1. double a = 3.6;  
  2. printf("%.2lf\n", a);//2表示保留两位小数  

输出结果3.60

 

有小数输出小数,没小数输出整数

%g

 

特别注意:中文符号和英文符号要对应一致,一般情况下都用英文符号(如中文逗号,和英文逗号,)

 

long long的使用

很多情况下的计算会超出int,比如求N!,N比较大的时候int就存不下了,这时候我们就要用long long。那么我们怎么去记int和long long的范围呢,有一个简单的记法,int范围-1e9到1e9,long long范围-1e18到1e18,这样就容易记了。

  1. long long x;  
  2. scanf("%lld", &x);  
  3. printf("%lld\n", x);  

 

 

字符的ASCII码

不要硬记,直接输出来看

printf("%d\n", 'a');

输出结果97

printf("%d\n", 'A');

输出结果65

 

特别注意:如果遇到需要ASCII码的题目的时候记住char字符和int值是可以相互转化的。

 

cin 和 cout

很多时候使用C++的输入输出写起来更简单,在应对一些输入输出量不是很大的题目的时候,我们会采用cin和cout来提高我们的解题速度。

比如求两个数的和:

  1. #include //输入输出函数的头文件  
  2.   
  3. int main() {  
  4.     int a, b;  
  5.     cin >> a >> b;  
  6.     cout << a + b;//输出两个数之和  
  7. }  

 

可以发现,C++的输入输出敲起来更快,这是我们会使用它来进行混合编程的原因之一。

另外,C++的string类对于字符串操作很方便,但是输入输出只能用cin、cout。

特别注意:大家一定平时练习的时候不要排斥混合编程,即C与C++语法混用,然后用C++提交。这样可以极大的帮助你以更快的速度解决一道你用纯C写半天才能解决的题目,留下充裕的时间去解决更多的题目。

友情提示:当输入或输出格式有特殊要求的时候,cin和cout不方便解决,那么我们还是使用scanf和printf来解决问题。要注意的是printf尽量不要和cout同时使用,会发生一些不可控的意外。

 

 

1.5头文件技巧

 

这里推荐一个万能头文件给大家

 

  1. #include   
  2. using namespace std;  

 

不过要看考试的评测机支不支持,绝大部分都是支持的。当然,我们还可以留一手,准备一个完整的头文件,在考试开始前敲上去就行。

 

  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10. #include   
  11. #include   
  12. using namespace std;  
  13.   
  14. int main() {  
  15.   
  16.     return 0;  
  17. }  

 

特别注意:头文件可以多,但是不能少,但是有一些头文件是不允许的,大部分OJ为了系统安全性考虑限制了一些特殊的API,导致一些头文件不能使用,比如windows.h。当然不同的OJ的安全策略也不尽相同,一般不涉及到系统函数的头文件一般都是可以使用的。

 

 

1.6数组使用技巧

数组除了可以存储数据以外,还可以用来进行标记。

 

例题:

输入N(N<=100)个数,每个数的范围> 0 并且 <= 100,请将每个不同的数从小到大输出并且输出它对应的个数。

样例输入

8

3 2 2 1 1 4 5 5

样例输出

1 2

2 2

3 1

4 1

5 2

 

代码如下

  1. #include   
  2. using namespace std;  
  3.   
  4. int f[105]={0};//注意,尽量将数组开在全局  
  5. int main() {  
  6.     int n,x;  
  7.     scanf("%d", &n);  
  8.     for (int i = 0; i < n; i++) {  
  9.         scanf("%d", &x);  
  10.         f[x]++;  
  11.     }  
  12.     for (int i = 0; i <= 100; i++) {  
  13.         if (f[i] > 0) printf("%d %d\n", i, f[i]);  
  14.     }  
  15.     return 0;  
  16. }  

在这个程序中,我们使用一个数组f记录每个值得个数,f[i]的值表示i这个数有多少个,初始的时候每个值的个数都是0。

 

数组的使用不一定从0开始,可以从任意下标开始,只要我们使用的时候对应上就行。

 

例如我们存储地图的时候

####

#.##

##@#

####

 

假设一个地图是这样的,我们要用二维字符数组来存储,我们可以像下面这样做。

 

  1. #include   
  2. using namespace std;  
  3.   
  4. char mpt[10][10];  
  5. int main() {  
  6.     for (int i = 1; i <= 4; i++) {  
  7.         scanf("%s", mpt[i] + 1);  
  8.         /* 不要用下面这种输入方式,否则会出问题,因为回车也算一个char字符 
  9.         for (int j = 1; j <= 4; j++) { 
  10.             scanf("%c", &mpt[i][j]); 
  11.         } 
  12.         */  
  13.     }  
  14.     for (int i = 1; i <= 4; i++) {  
  15.         for (int j = 1; j <= 4; j++) {  
  16.             printf("%c", mpt[i][j]);  
  17.         }  
  18.         printf("\n");  
  19.     }  
  20.     return 0;  
  21. }  

 

 

数组还可以嵌套使用

我们将上面那题改进一下

 

例题:

输入N(N<=100)个数,每个数的范围> 0 并且 <= 100,请将每个不同的数从小到大输出并且输出它对应的个数。如果多个值有相同的个数,输出值最大的那个。

样例输入

8

3 2 2 1 1 4 5 5

样例输出

4 1

5 2

 

代码如下

  1. #include   
  2. using namespace std;  
  3.   
  4. int f[105] = {0};  
  5. int p[105] = {0};//p[i]表示有i个这样的数的最大值是多少  
  6. int main() {  
  7.     int n,x;  
  8.     scanf("%d", &n);  
  9.     for (int i = 0; i < n; i++) {  
  10.         scanf("%d", &x);  
  11.         f[x]++;  
  12.     }  
  13.     for (int i = 0; i <= 100; i++) p[f[i]] = i;  
  14.     for (int i = 1; i <= 100; i++) {  
  15.         if (p[i] > 0) printf("%d %d\n", p[i], i);  
  16.     }  
  17.     return 0;  
  18. }  

 

 

1.7审时度势 — 复杂度与是否可做

在做题之前,我们要先判断这道题是否可做,对于简单的模拟题,大家肯定都知道,我能写出来就是可做,写不出来就是不可做。但是对于循环嵌套和算法题,我们就需要去判断思考自己设计的算法是否可以通过这道题。

不懂复杂度计算的同学去看一下数据结构课程的第一章,很简单的。

 

例如:我们写一个冒泡排序,它是两个for循环,时间复杂度是O(N^2)

那么在1S内我们最多可以对多少个数进行冒泡排序呢,N在1000 - 3000之间。

一般情况下我们可以默认评测机一秒内可以运行1e7条语句,当然这只是一个大概的估计,实际上每个服务器的性能不同,这个值都不同,但是一般都相差不大,差一个常数是正常的。

因此,我们可以这样做一个对应,下面是时限1S的情况

 

O(N)         N最大在500W左右

O(NlogN)     N最大在20W左右

O(N^2)       N最大在2000左右

O(N^2logN)   N最大700在左右

O(N^3)       N最大在200左右

O(N^4)       N最大在50左右

O(2^N)       N最大在24左右

O(N!)        N最大在10左右

如果是2S、3S对应的乘以2和3就可以。

 

特殊技巧:如果发现自己设计的算法不能题目要求的时限内解决问题,不要着急,可以先把这道题留一下,继续做其他题,然后看一下排行榜,有多少人过了这道题,如果过的人多,那么说明这道题可能数据比较水,直接暴力做,不要怕复杂度的问题,因为出题人可能偷懒或者失误了导致数据很水。考研机试的题目数据大部分情况都比较水,所以不要被复杂度吓唬住了,后面的章节会教大家面对不会更好的算法那来解决题目的时候,如何用优雅的技巧水过去。

 

举个简单的例子

题目要求你对10W个数进行排序

假设你只会冒泡排序,但是冒泡排序很明显复杂度太高了,但是有可能出题人偷懒,他构造测试数据最多只有100个,根本没有10W个,那么你就可以用冒泡排序通过这道题。

但是这种情况比较少见,一般至少都会有一组极限数据,所以可以先把这道题放着做其他题,然后再看看其他人能不能通过,如果很多人都过了,那么你就可以暴力试一下。

 

特别注意:空间复杂度一般不会限制,如果遇到了再想办法优化空间。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1.8 C++ STL的使用

 

C++的算法头文件里有很多很实用的函数,我们可以直接拿来用。

#include

 

排序

sort()函数:依次传入三个参数,要排序区间的起点,要排序区间的终点+1,比较函数。比较函数可以不填,则默认为从小到大排序。

 

使用示例

  1. #include   
  2. using namespace std;  
  3.   
  4. int a[105];  
  5. int main() {  
  6.     int n;  
  7.     scanf("%d", &n);  
  8.     for (int i = 0; i < n; i++) {  
  9.         scanf("%d", &a[i]);  
  10.     }  
  11.     sort(a, a+n);  
  12.     for (int i = 0; i < n; i++)  
  13.         printf("%d ", a[i]);  
  14.     return 0;  
  15. }  

 

查找

lower_bound()函数

upper_bound()函数

lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。

 

 

从小到大的排序数组中,

lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

 

从大到小的排序数组中,重载lower_bound()和upper_bound()

lower_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

 

使用示例

  1. #include  
  2. using namespace std;  
  3.   
  4. int cmp(int a,int b){  
  5.     return a>b;  
  6. }  
  7. int main(){  
  8.     int num[6]={1,2,4,7,15,34};  
  9.     sort(num,num+6);  //按从小到大排序  
  10.     int pos1=lower_bound(num,num+6,7)-num;      
  11.     //返回数组中第一个大于或等于被查数的值  
  12.     int pos2=upper_bound(num,num+6,7)-num;      
  13.     //返回数组中第一个大于被查数的值  
  14.     cout<" "<
  15.     cout<" "<
  16.     sort(num,num+6,cmp);       //按从大到小排序  
  17.     int pos3=lower_bound(num,num+6,7,greater<int>())-num;    
  18.     //返回数组中第一个小于或等于被查数的值  
  19.     int pos4=upper_bound(num,num+6,7,greater<int>())-num;    
  20.     //返回数组中第一个小于被查数的值  
  21.     cout<" "<
  22.     cout<" "<
  23.     return 0;  
  24. }  

 

优先队列

通过priority_queue q来定义一个储存整数的空的priority_queue。当然priority_queue可以存任何类型的数据,比如priority_queue q等等。

示例代码

  1. #include   
  2. #include   
  3. using namespace std;  
  4. int main() {  
  5.     priority_queue<int> q;//定义一个优先队列  
  6.     q.push(1);//入队  
  7.     q.push(2);  
  8.     q.push(3);  
  9.     while (!q.empty()) {//判读队列不为空  
  10.         cout << q.top() << endl;//队首元素  
  11.         q.pop();//出队  
  12.     }  
  13.     return 0;  
  14. }  

 

 

 

 

C++的STL标准模板库是一个非常重要的东西,可以极大的帮助你更快速解决题目。

 

vector

通过vector v来定义一个储存整数的空的vector。当然vector可以存任何类型的数据,比如vector v等等。

 

示例代码

 

  1. #include     
  2. #include     
  3. using namespace std;    
  4. int main() {    
  5.     vector<int> v;//定义一个空的vector  
  6.     for (int i = 1; i <= 10; ++i) {    
  7.         v.push_back(i * i); //加入到vector中  
  8.     }    
  9.     for (int i = 0; i < v.size(); ++i) {    
  10.         cout << v[i] << " ";  //访问vecotr的元素  
  11.     }    
  12.     cout << endl;    
  13.     return 0;    
  14. }    

 

queue

 

通过queue q来定义一个储存整数的空的queue。当然queue可以存任何类型的数据,比如queue q等等。

示例代码

 

  1. #include   
  2. #include   
  3. using namespace std;  
  4. int main() {  
  5.     queue<int> q;//定义一个队列  
  6.     q.push(1);//入队  
  7.     q.push(2);  
  8.     q.push(3);  
  9.     while (!q.empty()) {//当队列不为空  
  10.         cout << q.front() << endl;//取出队首元素  
  11.         q.pop();//出队  
  12.     }  
  13.     return 0;  
  14. }  

 

 

stack

通过stack S来定义一个全局栈来储存整数的空的stack。当然stack可以存任何类型的数据,比如stack S等等。

示例代码

  1. #include   
  2. #include   
  3. using namespace std;  
  4. stack<int> S;//定义一个栈  
  5. int main() {  
  6.     S.push(1);//入栈  
  7.     S.push(10);  
  8.     S.push(7);  
  9.     while (!S.empty()) {//当栈不为空  
  10.       cout << S.top() << endl;//输出栈顶元素  
  11.       S.pop();//出栈  
  12.     }  
  13.     return 0;  
  14. }  

 

map

通过map dict来定义一个key:value映射关系的空的map。当然map可以存任何类型的数据,比如map m等等。

 

示例代码

  1. #include   
  2. #include   
  3. #include   
  4. using namespace std;  
  5. int main() {  
  6.     mapint> dict;//定义一个map  
  7.     dict["Tom"] = 1;//定义映射关系  
  8.     dict["Jone"] = 2;  
  9.     dict["Mary"] = 1;  
  10.     if (dict.count("Mary")) {//查找map  
  11.         cout << "Mary is in class " << dict["Mary"];  
  12.     }  
  13.     //使用迭代器遍历map的key和value  
  14.     for (mapint>::iterator it = dict.begin(); it != dict.end(); ++it) {  
  15.         cout << it->first << " is in class " << it->second << endl;  
  16.     }  
  17.     dict.clear();//清空map  
  18.     return 0;  
  19. }  

 

set

通过set country来定义一个储存字符串的空的set。当然set可以存任何类型的数据,比如set s等等。

 

示例代码

  1. #include   
  2. #include   
  3. using namespace std;  
  4. int main() {  
  5.     set country;//定义一个存放string的集合  
  6.     country.insert("China");//插入操作  
  7.     country.insert("America");  
  8.     country.insert("France");  
  9.     set::iterator it;  
  10.     //使用迭代器遍历集合元素  
  11.     for (it = country.begin(); it != country.end(); ++it) {  
  12.         cout << * it  << " ";  
  13.     }  
  14.     cout << endl;  
  15.     country.erase("American");//删除集合内的元素  
  16.     country.erase("England");  
  17.     if (country.count("China")) {//统计元素个数  
  18.         cout << "China in country." << endl;  
  19.     }  
  20.     country.clear();//清空集合  
  21.     return 0;  
  22. }  

 

1.9多组输入的问题

对有的题目来说,可能需要多组输入。

多组输入是什么意思呢?一般的题目我们输入一组数据,然后直接输出程序就结束了,但是多组输入的话要求我们可以循环输入输出结果。

 

例题:

输入两个数,输出两个数的和,要求多组输入。

样例输入

1 2

3 7

10 24

样例输出

3

10

34

 

C循环读入代码如下

  1. #include   
  2. using namespace std;  
  3.   
  4. int main() {  
  5.     int a, b;  
  6.     while (scanf("%d%d", &a, &b) != EOF) {  
  7.         printf("%d\n", a+b);  
  8.     }  
  9.     return 0;  
  10. }  

 

特别注意:不能使用while(1)这样死循环,!=EOF的意思一直读取到文件末尾(End of file)

另外,多组输入一定要注意初始化问题,数组和变量的初始化要放在while循环内,否则上一次的运算的结果会影响当前的结果。

 

C++循环读入代码如下

  1. #include   
  2. using namespace std;  
  3.   
  4. int main() {  
  5.     int a, b;  
  6.     while (cin >> a >> b) {  
  7.         cout << a + b << endl;  
  8.     }  
  9.     return 0;  
  10. }  

 

Java循环读入代码如下

  1. Scanner stdin = new Scanner(System.in);  
  2. while (stdin.hasNext()) {  
  3.     String s = stdin.next();  
  4.     int n = stdin.nextInt();  
  5.     double b = stdin.nextDouble();  
  6. }  

 

Python循环读入代码如下

  1. while True:  
  2.     try:  
  3.         a, b = map(int, input().split())  
  4.         c = a+b  
  5.         print(c)  
  6.     except: #读到文件末尾抛出异常结束循环  
  7.         break  

 

 

 

 

 

 

 

 

 

第二章 入门经典

 

我们根据全国上百所院校的历年真题进行分析,总结了其中常见的题型,最常考的知识点。

学会这一章,在机试难度低的学校基本上可以拿到60-80分左右的成绩,在机试难度中等的学校,也可拿到40-60分左右的成绩,在机试难度高的学校亦可将签到题做出来,拿到20-40分的成绩。

所以,认真看完这一章的内容对你的帮助很大,加油,fighting!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.1 简单模拟

在考研机试中,有一类很常见的题型叫做简单模拟。顾名思义,就是不需要去考虑什么算法,直接按照题目的意思进行模拟计算就行。

 

促销计算

题目描述:

某百货公司为了促销,采用购物打折的优惠方法,每位顾客一次购物:在1000元以上者,按9.5折优惠;在2000以上者,按9折优惠;在3000以上者,按8.5折优惠;在5000以上者,按8折优惠;编写程序,购物款数,计算并输出优惠价。

输入样例#:

850

1230

5000

3560

输出样例#:

discount=1,pay=850

discount=0.95,pay=1168.5

discount=0.8,pay=4000

discount=0.85,pay=3026

题目来源:

DreamJudge 1091

 

解题分析:根据题目的意思,我们知道就是按照题意去进行打折优惠的计算,只需要判断输入的数值在哪个区间该用什么优惠去计算就好了。

 

参考代码

  1. #include //万能头文件  
  2. using namespace std;  
  3.   
  4. int main() {  
  5.     double a;  
  6.     scanf("%lf", &a);  
  7.     //使用%g可以自动去掉小数点后多余的0 如果是整数则显示整数  
  8.     if (a < 1000) printf("discount=1,pay=%g\n", a);  
  9.     if (a >= 1000 && a < 2000) printf("discount=0.95,pay=%g\n", a*0.95);  
  10.     if (a >= 2000 && a < 3000) printf("discount=0.9,pay=%g\n", a*0.9);  
  11.     if (a >= 3000 && a < 5000) printf("discount=0.85,pay=%g\n", a*0.85);  
  12.     if (a >= 5000) printf("discount=0.8,pay=%g\n", a*0.8);  
  13.     return 0;  
  14. }  

 

题型总结

简单模拟这类题目在考试中很常见,属于送分签到的题目。所有的考生,注意了,这类题必须会做。

对于简单模拟这一类的题目,怎么去练习提高呢?

很简单,在DreamJudge上多做题就行了。那么要达到什么样的标准呢?

如果你想拿高分甚至满分,平时训练的时候,这类题尽量要在8分钟内解决。

如果你只是想拿个还不错的成绩,这类题AC的时间尽量不要超过15分钟,一定要记住,最坏情况不能超过20分钟,如果超过了,说明你平时做的题还是太少了。

在考试的过程中,大多数考生都会紧张,有些考生甚至会手抖,导致敲多某个字母,然后又调试半天,找半天错,会导致比平时解决同样难度的问题时长多一倍甚至更多,所以平时就要注意,做题千万不能太慢了,不然没有足够的时间来解决其他的题目哦。

 

练习题目

DreamJudge 1133 求1到n的和

DreamJudge 1043 计算Sn

DreamJudge 1040 利润提成

 

 

2.2 进制转换类问题

进制转换类的题目在绝大多数学校都是必考题目之一,这类题目的既基础又灵活,能看出学生的编程功底,所以这类题目一定要掌握。

 

总的来说,跟进制相关的题目可以分为以下几种题型

 

1、反序数:输入一个整数如123,将其转换为反序之后的整数321

2、10进制转2进制:将一个10进制整数转化为一个2进制的整数

例如:7转换为111

3、10进制转16进制:将一个10进制整数转化为一个18进制的整数

例如:10转换为A

4、10进制转x进制:将一个10进制整数转化为一个x进制的整数

解析:这是前面两个的一种通解,如果会前面两种那么这个自然也触类旁通。

5、x进制转10进制:将一个x进制整数转化为一个10进制的整数

解析:这是上一种情况的反例,看代码之后相信也能容易理解。

6、x进制转y进制:将一个x进制整数转化为一个y进制的整数

解析:遇到这种情况,可以拆解为x先转为10进制,然后再将10进制转为y进制。

7、字符串转浮点数

例如:有一串字符串31.25 将其转换为一个浮点数,可以先转整数部分,再转小数部分,最后相加即可。

8、浮点数转字符串

例如:有一个浮点数23.45将其转换为一个字符串进行存储,可以将整数和小数拆开再合并成一个字符串。

9、字符串转整型和整形转字符串

解析:直接用atoi函数和itoa函数即可。

 

 

 

反序数代码

  1. #include   
  2.   
  3. int main() {  
  4.     int n;  
  5.     scanf("%d", &n);  
  6.     int ans = 0;//将反序之后的答案存在这里  
  7.     while (n > 0) {//将n逐位分解  
  8.         ans *= 10;  
  9.         ans += (n % 10);  
  10.         n /= 10;  
  11.     }  
  12.     printf("%d\n", ans);  
  13.     return 0;  
  14. }  

 

 

10进制转x进制代码(x小于10的情况)

  1. #include   
  2.   
  3. int main() {  
  4.     int n, x;  
  5.     int s[105];  
  6.     //输入10进制n 和 要转换的进制x  
  7.     scanf("%d%d", &n, &x);  
  8.     int cnt = 0;//数组下标  
  9.     while (n > 0) {//将n逐位分解  
  10.         int w = (n % x);  
  11.         s[cnt++] = w;  
  12.         n /= x;  
  13.     }  
  14.     //反序输出  
  15.     for (int i = cnt - 1; i >= 0; i--) {  
  16.         printf("%d", s[i]);  
  17.     }  
  18.     printf("\n");  
  19.     return 0;  
  20. }  

10进制转x进制代码(通用版)

  1. #include   
  2.   
  3. int main() {  
  4.     int n, x;  
  5.     char s[105];//十进制以上有字符,所以用char存储  
  6.     //输入10进制n 和 要转换的进制x  
  7.     scanf("%d%d", &n, &x);  
  8.     int cnt = 0;//数组下标  
  9.     while (n > 0) {//将n逐位分解  
  10.         int w = (n % x);  
  11.         if (w < 10) s[cnt++] = w + '0';//变成字符需要加'0'  
  12.         else s[cnt++] = (w - 10) + 'A';//如果转换为小写则加'a'  
  13.         //如果大于10则从A字符开始  
  14.         n /= x;  
  15.     }  
  16.     //反序输出  
  17.     for (int i = cnt - 1; i >= 0; i--) {  
  18.         printf("%c", s[i]);  
  19.     }  
  20.     printf("\n");  
  21.     return 0;  
  22. }  

 

 

x进制转10进制(x为2时)

  1. #include   
  2. #include   
  3.   
  4. int main() {  
  5.     char s[105];  
  6.     //输入二进制字符串  
  7.     scanf("%s", &s);  
  8.     int ans = 0;//  
  9.     int len = strlen(s);  
  10.     for (int i = 0; i < len; i++) {  
  11.         if (s[i] == '0') {  
  12.             ans = ans * 2;  
  13.         }  
  14.         else {  
  15.             ans = ans * 2 + 1;  
  16.         }  
  17.     }  
  18.     printf("%d\n", ans);  
  19.     return 0;  
  20. }  

 

 

x进制转10进制(通用版

  1. #include   
  2. #include   
  3.   
  4. int main() {  
  5.     char s[105];  
  6.     int x;  
  7.     //输入X进制字符串 和 代表的进制x  
  8.     scanf("%s%d", &s, &x);  
  9.     int ans = 0;//  
  10.     int len = strlen(s);  
  11.     for (int i = 0; i < len; i++) {  
  12.         ans = ans * x;  
  13.         if (s[i] >= '0' && s[i] <= '9') ans += (s[i] - '0');  
  14.         else ans += (s[i] - 'A') + 10;  
  15.     }  
  16.     printf("%d\n", ans);  
  17.     return 0;  
  18. }  

 

 

x进制转y进制(通用版)

  1. #include   
  2. #include   
  3.   
  4. int main() {  
  5.     char s[105];  
  6.     int x, y;  
  7.     //输入二进制字符串 和 代表的进制x 以及要转换的进制y  
  8.     scanf("%s%d%d", &s, &x, &y);  
  9.     int ans = 0;  
  10.     int len = strlen(s);  
  11.     for (int i = 0; i < len; i++) {  
  12.         ans = ans * x;  
  13.         if (s[i] >= '0' && s[i] <= '9') ans += (s[i] - '0');  
  14.         else ans += (s[i] - 'A') + 10;  
  15.     }  
  16.     char out[105];  
  17.     int cnt = 0;  
  18.     while (ans > 0) {  
  19.         int w = (ans % y);  
  20.         if (w < 10) out[cnt++] = w + '0';  
  21.         else out[cnt++] = (w-10) + 'A';  
  22.         ans /= y;  
  23.     }  
  24.     for (int i = cnt - 1; i >= 0; i--) {  
  25.         printf("%c", out[i]);  
  26.     }  
  27.     printf("\n");  
  28.     return 0;  
  29. }  

 

 

题型总结

这类题目任他千变万化,本质上都是不变的。就是数位的拆解与合并,拆解很明显就是两步,先取模然后除取整,合并就是先乘后加。只要掌握了以上的几类变化,不管题目如何变化,你都立于了不败之地。

 

题目练习

DreamJudge 1454 反序数

DreamJudge 1178 进制转换

DreamJudge 1259 进制转换2

DreamJudge 1176 十进制和二进制

DreamJudge 1380 二进制数

DreamJudge 1417 八进制

DreamJudge 1422 进制转换3

DreamJudge 1097 负二进制

 

 

2.3 排版类问题

排版类问题也是机试中经常出现的题目,这类题目主要考验考生对代码的掌控程度。表面上看起来很简单,但是对于大部分没有认真研究过的同学来学,这些题可能会搞半天才能搞出来。

 

总的来说,排版类的题目可以以下几种题型为代表。

  1. 输出字符棱形 DreamJudge 1473

这类题目的变形可以是输出长方形、三角形、梯形等形状。

2、旋转数字输出

3、矩阵顺/逆指针旋转

4、矩阵翻转

这类题目的变形可以是轴对称翻转、中心对称翻转等。

5、杨辉三角形

6、2048问题

 

 

以上,我们选择其中输出字符棱形杨辉三角形进行详细讲解,其他题型我们给出解题思路以及题目编号,大家可以在本节后面的练习题目里找到并完成。如果自己无法理解并完成题目,请加入我们的机试交流群进行提问交流。

 

字符棱形

题目描述:

输入一个整数n表示棱形的对角半长度,请你用*把这个棱形画出来。

输入:3

输出:

  *

 ***

*****

 ***

  *

输入样例#:

1

输出样例#:

*

题目来源:

DreamJudge 1473

 

解题分析:对于这类题目,我们可以将它进行分解。从中间切开,上面一个三角形,下面一个三角形。那么问题就转化为了如何输出三角形,我们可以利用两个for循环控制来输出三角形。

 

参考代码

  1. #include   
  2.   
  3. int main() {  
  4.     int n;  
  5.     scanf("%d", &n);  
  6.     //上三角  
  7.     for (int i = 1; i <= n; i++) {  
  8.         for (int j = 1; j <= n - i; j++) {  
  9.             printf(" ");  
  10.         }  
  11.         for (int j = n - i + 1; j < n + i; j++) {  
  12.             printf("*");  
  13.         }  
  14.         printf("\n");  
  15.     }  
  16.     //下三角  下三角只需要将上三角反过来输出就行  
  17.     for (int i = n - 1; i >= 1; i--) {  
  18.         for (int j = 1; j <= n - i; j++) {  
  19.             printf(" ");  
  20.         }  
  21.         for (int j = n - i + 1; j < n + i; j++) {  
  22.             printf("*");  
  23.         }  
  24.         printf("\n");  
  25.     }  
  26.     return 0;  

 

 

杨辉三角形

题目描述:

提到杨辉三角形.大家应该都很熟悉.这是我国宋朝数学家杨辉在公元1261年著书《详解九章算法》提出的。 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1 我们不难其规律: S1:这些数排列的形状像等腰三角形,两腰上的数都是1 S2:从右往左斜着看,第一列是1,1,1,1,1,1,1;第二列是,1,2,3,4,5,6;第三列是1,3,6,10,15;第四列是1,4,10,20;第五列是1,5,15;第六列是1,6……。 从左往右斜着看,第一列是1,1,1,1,1,1,1;第二列是1,2,3,4,5,6……和前面的看法一样。我发现这个数列是左右对称的。 S3:上面两个数之和就是下面的一行的数。 S4:这行数是第几行,就是第二个数加一。…… 现在要求输入你想输出杨辉三角形的行数n; 输出杨辉三角形的前n行.

输入描述:

输入你想输出杨辉三角形的行数n(n<=20);当输入0时程序结束.

输出描述:

对于每一个输入的数,输出其要求的三角形.每两个输出数中间有一个空格.每输完一个三角形换行.

输入样例#:

5

输出样例#:

1

1 1

1 2 1

1 3 3 1

1 4 6 4 1

题目来源:

DreamJudge 1062

 

解题分析:这是一道特别经典的题目,我们只需要按照题意用二维数组去计算即可。对于任意一个数a[i][j],都有a[i][j] = a[i-1][j] + a[i-1][j-1];

 

参考代码

  1. #include   
  2. int main() {  
  3.     int a[21][21] = {0};//数组里的所有值初始化为0  
  4.     int n;  
  5.     while (scanf("%d", &n) != EOF) {  
  6.         if (n == 0) break;  
  7.         a[1][1] = 1;  
  8.         for (int i = 2; i <= n; i++) {  
  9.             for (int j = 1; j <= i; j++) {  
  10.                 a[i][j] = a[i-1][j] + a[i-1][j-1];  
  11.             }  
  12.         }  
  13.         for (int i = 1; i <= n; i++) {  
  14.             for (int j = 1; j <= i; j++) {  
  15.                 printf("%d ", a[i][j]);  
  16.             }  
  17.             printf("\n");  
  18.         }  
  19.     }  
  20.     return 0;  
  21. }  

 

 

题型总结:这类题目尽量在平时练习,解法主要就是把一个大问题进行分解,一部分一部分的实现。在考试的时候遇到,千万不要急,将问题进行分解,找到其中的规律,然后再写出来。当然,如果平时就有练习,那就不用担心了。

 

 

 

 

 

 

练习题目

DreamJudge 1392 杨辉三角形 - 西北工业大学

DreamJudge 1377 旋转矩 - 北航

DreamJudge 1216 旋转方阵

DreamJudge 1221 旋转矩阵

DreamJudge 1472 2048游戏

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.4 日期类问题

日期类的题目也是常考的题目,这类题目一般都为以下几种考法。

 

1、判断某年是否为闰年

2、某年某月某日是星期几

变形问法:某日期到某日期之间有多少天

3、某天之后x天是几月几日

4、10:15分之后x分钟是几点几分

变形问法:某点到某点之间有多少分或多少秒

 

注意输入时候一般用scanf解析输入值

如:2019-11-8 2019-11-08 2019/11/8 10:10

  1. int year, month, day;  
  2. scanf("%d-%d-%d", &year, &month, &day);  
  3. scanf("%d/%d/%d", &year, &month, &day);  
  4. int hour, minute;  
  5. scanf("%d:%d", &hour, &minute);  

 

 

日期

题目描述:

定义一个结构体变量(包括年、月、日),编程序,要求输入年月日,计算并输出该日在本年中第几天。

输入描述:

输输入三个整数(并且三个整数是合理的,既比如当输入月份的时候应该在1 至12 之间,不应该超过这个范围)否则输出Input error!

输出描述:

输出一个整数.既输入的日期是本月的第几天。

输入样例#:

1985 1 20

2006 3 12

输出样例#:

20

71

题目来源:

DreamJudge 1051

 

 

解题分析:这个题目的考点在于两个地方,一个是每个月的天数都不一样,另一个是2月如果是闰年则多一天,最后我们还要判断输入的日期是否存在,如果不存在则输出Input error!

 

参考代码

  1. #include   
  2.   
  3. struct node {  
  4.     int year, month, day;  
  5. }p;  
  6. int f[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};  
  7. int main() {  
  8.     while (scanf("%d%d%d", &p.year, &p.month, &p.day) != EOF) {  
  9.         //判断是否闰年  
  10.         if ((p.year%400== 0)||(p.year%4==0)&&(p.year%100!=0)) {  
  11.             f[2] = 29;  
  12.         }  
  13.         else f[2] = 28;  
  14.         int flag = 0;  
  15.         //判断月份输入是否合法  
  16.         if (p.month < 1 || p.month > 12) flag = 1;  
  17.         //判断天的输入是否合法  
  18.         for (int i = 1; i <= 12; i++) {  
  19.             if (p.day < 0 || p.day > f[i]) {  
  20.                 flag = 1;  
  21.             }  
  22.         }  
  23.         if (flag) {  
  24.             printf("Input error!\n");  
  25.             continue;  
  26.         }  
  27.         int ans = p.day;  
  28.         for (int i = 1; i < p.month; i++) {  
  29.             ans += f[i];  
  30.         }  
  31.         printf("%d\n", ans);  
  32.     }  
  33.     return 0;  
  34. }  

 

 

题型总结

日期类的题目就是要特别注意闰年的判断,这些题目一般都是考察代码细节的把握,时间类的题目注意时间的转换,1天=24小时,1小时=60分,1分=60秒。

 

特别注意:一天之内时针和分针会重合22次,而不是24次。

 

 

 

 

 

 

练习题目

DreamJudge 1011 日期

DreamJudge 1290 日期差值

DreamJudge 1410 打印日期

DreamJudge 1437 日期类

DreamJudge 1446 日期累加

DreamJudge 1053 偷菜时间表

 

2.5 字符串类问题

字符串类的问题也是各个院校必考的题型之一,基本上有以下这些考点:

  1. 统计字符个数
  2. 单词首字母大写
  3. 统计子串出现次数

解析:考察大家基础的字符串遍历能力。

  1. 文本加密/解密

解析:通过循环往后移动x位或直接给一个映射表是比较常见的考法。

  1. 文本中的单词反序

解析:灵活使用string可以秒杀这类题目,当然也可以用字符串一步步解析。

  1. 删除字符串(大小写模糊)

解析:如果大小写不模糊,那么就是直接找到之后删除。大小写模糊的话,只是多一个判断。

 

加密算法

题目描述:

编写加密程序,加密规则为:将所有字母转化为该字母后的第三个字母,即A->D、B->E、C->F、......、Y->B、Z->C。小写字母同上,其他字符不做转化。输入任意字符串,输出加密后的结果。

例如:输入"I love 007",输出"L oryh 007"

输入描述:

输入一行字符串,长度小于100。

输出描述:

输出加密之后的结果。

输入样例#:

I love 007

输出样例#:

L oryh 007

题目来源:

DreamJudge 1014

 

题目解析:这是一道很常见的加解密考法,往后移动3位是这道题的核心,我们只需要按照题意将大写字母、小写字母、和其他分开进行处理就可以,具体看代码。

参考代码

  1. #include   
  2. #include   
  3.   
  4. int main() {  
  5.     char s[105];  
  6.     gets(s);//输入一行文本用gets  
  7.     int len = strlen(s);  
  8.     for (int i = 0; i < len; i++) {  
  9.         if (s[i] >= 'A' && s[i] <= 'Z') {  
  10.             s[i] += 3;  
  11.             if (s[i] > 'Z') s[i] -= 26;//溢出循环  
  12.         }  
  13.         else if (s[i] >= 'a' && s[i] <= 'z') {  
  14.             s[i] += 3;  
  15.             if (s[i] > 'z') s[i] -= 26;//溢出循环  
  16.         }  
  17.         else {  
  18.             continue;  
  19.         }  
  20.     }  
  21.     puts(s);  
  22.     return 0;  
  23. }  

练习题目

DreamJudge 1012 字符移动

DreamJudge 1292 字母统计

DreamJudge 1240 首字母大写

DreamJudge 1394 统计单词

DreamJudge 1027 删除字符串2

 

2.6 排序类问题

排序类的问题基本上是每个学校必考的知识点,所以它的重要性不言而喻。

如果你在网上一查,或者看数据结构书,十几种排序算法可以把你吓的魂不守舍。表面上看各种排序都有其各自的特点,那是不是我们需要掌握每一种排序呢?

答案自然是否定的。我们一种排序也不需要掌握,你需要会用一个sort函数就可以了,正所谓一个sort走天下。

 

sort函数本质上是封装了快速排序,但是它做了一些优化,所以你只管用它就行了。

复杂度为:nlogn

所以sort可以对最大30W个左右的元素进行排序,可以应对考研机试中的99.9%的情况。

 

sort函数的用法

sort()函数:依次传入三个参数,要排序区间的起点,要排序区间的终点+1,比较函数。比较函数可以不填,则默认为从小到大排序。

 

sort函数有两个常见的应用场景

  1. 自定义函数排序
  2. 多级排序

 

 

成绩排序

题目描述:

输入任意(用户,成绩)序列,可以获得成绩从高到低或从低到高的排列,相同成绩都按先录入排列在前的规则处理。

示例:

jack      70

peter     96

Tom       70

smith     67

从高到低  成绩

peter     96

jack      70

Tom       70

smith     67

从低到高

smith     67

jack      70

Tom      70

peter     96

输入描述:

输入多行,先输入要排序的人的个数,然后输入排序方法0(降序)或者1(升序)再分别输入他们的名字和成绩,以一个空格隔开

输出描述:

按照指定方式输出名字和成绩,名字和成绩之间以一个空格隔开

输入样例#:

3

0

fang 90

yang 50

ning 70

输出样例#:

fang 90

ning 70

yang 50

题目来源:

DreamJudge 1151

 

题目解析:这题唯一的一个考点在于稳定排序,sort排序是不稳定的,排序之后相对次序有可能发生改变。解决这个问题有两个方法,一个是用stable_sort函数,它的用法和sort一样,但是它是稳定的,所以如果我们遇到有稳定的需求的排序时,可以用它。另一个方法是给每一个输入增加一个递增的下标,然后二级排序,当值相同时,下标小的排在前面。

 

参考代码(稳定排序)

  1. #include   
  2. using namespace std;  
  3.   
  4. struct Student {  
  5.     string name;  
  6.     int grade;  
  7. }stu[1005];  
  8. //从大到小排序  
  9. bool compareDesc(Student a,Student b) {  
  10.     return a.grade > b.grade;  
  11. }  
  12. //从小到大排序  
  13. bool compareAsc(Student a,Student b) {  
  14.     return a.grade < b.grade;  
  15. }  
  16. int main() {  
  17.     int n,order;  
  18.     while(cin>>n) {  
  19.         cin>>order;  
  20.         for(int i=0;i
  21.             cin>>stu[i].name>>stu[i].grade;  
  22.         }  
  23.         if(order==0)  
  24.             stable_sort(stu,stu+n,compareDesc);  
  25.         else  
  26.             stable_sort(stu,stu+n,compareAsc);  
  27.         for(int i=0;i
  28.             cout<" "<
  29.         }  
  30.     }  
  31.     return 0;  
  32. }  

 

参考代码(标记id)

  1. #include   
  2. using namespace std;  
  3.   
  4. struct Student {  
  5.     string name;  
  6.     int grade, id;  
  7. }stu[1005];  
  8. //从大到小排序  
  9. bool compareDesc(Student a,Student b) {  
  10.     if (a.grade == b.grade) return a.id < b.id;  
  11.     return a.grade > b.grade;  
  12. }  
  13. //从小到大排序  
  14. bool compareAsc(Student a,Student b) {  
  15.     if (a.grade == b.grade) return a.id < b.id;  
  16.     return a.grade < b.grade;  
  17. }  
  18. int main() {  
  19.     int n,order;  
  20.     while(cin>>n) {  
  21.         cin>>order;  
  22.         for(int i=0;i
  23.             cin>>stu[i].name>>stu[i].grade;  
  24.             stu[i].id = i;//通过标记ID进行判断  
  25.         }  
  26.         if(order==0)  
  27.             sort(stu,stu+n,compareDesc);  
  28.         else  
  29.             sort(stu,stu+n,compareAsc);  
  30.         for(int i=0;i
  31.             cout<" "<
  32.         }  
  33.     }  
  34.     return 0;  
  35. }  

 

 

 

 

 

排序

题目描述:

输入n个数进行排序,要求先按奇偶后按从小到大的顺序排序。

输入描述:

第一行输入一个整数n,表示总共有多少个数,n<=1000。

第二行输入n个整数,用空格隔开。

输出描述:

输出排序之后的结果。

输入样例#:

8

1 2 3 4 5 6 7 8

输出样例#:

1 3 5 7 2 4 6 8

题目来源:

DreamJudge 1010

 

题目解析:题目要求我们按照奇数在前偶数在后的排序方法,同为奇数或同为偶数再从小到大排序。我们有两种简便的方法可以解决这个问题,其一是我们将奇数和偶数分离开来,然后分别排好序,再合并在一起。其二是使用sort进行二级排序,这里我们采用第二种方法进行演示。

 

参考代码

  1. #include   
  2. using namespace std;  
  3.   
  4. bool cmp(int a,int b){  
  5.     if(a % 2 == b % 2)//如果同奇同偶  
  6.         return a < b;//直接从小到大排序  
  7.     else//如果奇偶性不同  
  8.         return (a%2) > (b%2);//奇数在偶数前  
  9. }  
  10. int main() {  
  11.     int n;  
  12.     int a[1005] = {0};  
  13.     cin >> n;  
  14.     for (int i = 0; i < n; i++) {  
  15.         cin >> a[i];  
  16.     }  
  17.     sort(a, a+n, cmp);  
  18.     for(int i = 0; i < n; i++) {  
  19.         cout << a[i] << " ";  
  20.     }  
  21.     cout << endl;  
  22.     return 0;  

 

 

小结:由上面可以看出,只要我们掌握好sort的用法,不管什么样的花里胡哨的排序,我们都可以一力破之。

 

一些特殊的排序题

1、如果题目给的数据量很大,上百万的数据要排序,但是值的区间范围很小,比如值最大只有10万,或者值的范围在1000W到1010W之间,对于这种情况,我们可以采用空间换时间的计数排序。

2、字符串的字典序排序是一个常见的问题,需要掌握,也是用sort。

下面两种情况了解即可,追求满分的同学需要掌握

3、如果题目给你一个数的序列,要你求逆序数对有多少,这是一个经典的问题,解法是在归并排序合并是进行统计,复杂度可以达到nlogn。如果数据量小,直接冒泡排序即可。

4、如果题目让你求top10,即最大或最小的10个数,如果数据量很大,建议使用选择排序,也就是一个一个找,这样复杂度比全部元素排序要低。

5、如果题目给的数据量有几百万,让你从中找出第K大的元素,这时候sort是会超时的。解法是利用快速排序的划分的性质,进入到其中一个分支继续寻找,

 

以上都是一些数据很特殊且数据量非常大的情况下的解决方案。

 

 

 

练习题目

 

DreamJudge 1106 排序2

DreamJudge 1159 成绩排序2.0

DreamJudge 1217 国名排序

DreamJudge 1227 日志排序

DreamJudge 1248 整数奇偶排序

DreamJudge 1254 字符串排序

DreamJudge 1255 字符串排序2

DreamJudge 1261 字符串排序3

DreamJudge 1294 后缀子串排序

DreamJudge 1310 奥运排序问题

DreamJudge 1338 EXCEL排序

DreamJudge 1360 字符串内排序

DreamJudge 1399 排序 - 华科

DreamJudge 1400 特殊排序

DreamJudge 1404 成绩排序 - 华科

DreamJudge 1412 大整数排序

 

 

 

 

 

 

 

2.7 查找类问题

查找是一类我们必须掌握的算法,它不仅会在题目中直接考察,同时也可能是其他算法中的重要组成部分。本章中介绍的查找类问题都是单独的基础查找问题,对于这类基础查找的问题,我们应该将它完全掌握。

 

查找类题目一般有以下几种考点

  1. 数字查找给你一堆数字,让你在其中查找x是否存在

题目变形:如果x存在,请输出有几个。

  1. 字符串查找给你很多个字符串,让你在其中查找字符串s是否存在

 

顺序查找就不说了,这个大家会。

 

什么时候不能用顺序查找呢?

很明显,当满足下面这种情况的时候

  1. 数据量特别大的时候,比如有10W个元素。
  2. 查询次数很多的时候,比如要查询10W次。

 

遇到这类题大多数人的想法是先sort排序,然后二分查找,这是一个很常规的解决这类问题的方法。

但是,我们不推荐你这么做,我们有更简单易用且快速的方法。我们推荐你了解并使用map容器。

 

前面介绍过map,它是STL的一种关联式容器,它的底层是红黑树实现的,也就意味着它的插入和查找操作都是log级别的。

相信每一个用过map的同学,都会情不自禁的说一句,map真香!

 

 

 

查找学生信息2

题目描述:

输入N个学生的信息,然后进行查询。

输入描述:

输入的第一行为N,即学生的个数(N<=1000)

接下来的N行包括N个学生的信息,信息格式如下:

01 李江 男 21

02 刘唐 男 23

03 张军 男 19

04 王娜 女 19

然后输入一个M(M<=10000),接下来会有M行,代表M次查询,每行输入一个学号,格式如下:

02

03

01

04

输出描述:

输出M行,每行包括一个对应于查询的学生的信息。

如果没有对应的学生信息,则输出“No Answer!”

输入样例#:

4

01 李江 男 21

02 刘唐 男 23

03 张军 男 19

04 王娜 女 19

5

02

03

01

04

03

输出样例#:

02 刘唐 男 23

03 张军 男 19

01 李江 男 21

04 王娜 女 19

03 张军 男 19

题目来源:

DreamJudge 1476

 

题目解析:对于这类查询量大的题目,我们有两种方法来解决这个问题。第一是将学号先排好序,然后使用二分查找,但是很多同学写二分的时候容易出现问题,而且代码量也比较大,我们不推荐这种做法。推荐大家使用map来解决这类问题,基本上map可以通过99.9%的这类题目。

 

参考代码

  1. #include   
  2. using namespace std;  
  3.   
  4. struct node{  
  5.     string num;  
  6.     string name;  
  7.     string sex;  
  8.     int age;  
  9. };  
  10. int main(){  
  11.     int n,q;  
  12.     map M;//定义一个map映射  
  13.     while(scanf("%d", &n)!=EOF){  
  14.         for(int i=0;i
  15.             node tmp;  
  16.             cin>>tmp.num>>tmp.name>>tmp.sex>>tmp.age;  
  17.             M[tmp.num] = tmp;//将学号指向对应的结构体  
  18.         }  
  19.         scanf("%d", &q);  
  20.         for(int i=0;i
  21.             string num;  
  22.             cin>>num;  
  23.             if((M.find(num))!=M.end())//find查找 如果找不到则返回末尾  
  24.                 cout<" "<" "<" "<
  25.             else  
  26.                 cout<<"No Answer!"<
  27.         }  
  28.     }  
  29.     return 0;  
  30. }  

 

可以发现,用map解决这个题目的时候,不用去考虑字符串排序的问题,也不用想二分查找会不会写出问题,直接用map,所有的烦恼都没有了,而且它的复杂度和二分查找是一个量级的。

 

上面讲的是一类静态查找的问题,实际中为了增加思维难度或代码难度,会经过一定的改变变成动态查找问题。

 

动态查找问题

题目描述:

有n个整数的集合,想让你从中找出x是否存在。

输入描述:

第一行输入一个正整数n(n < 100000)

第二行输入n个正整数,用空格隔开。

第三行输入一个正整数q(q<100000),表示查询次数。

接下来输入q行,每行一个正整数x,查询x是否存在。

输出描述:

如果x存在,请输出find,如果不存在,请输出no,并将x加入到集合中。

输入样例#:

5

1 2 3 4 5

3

6

6

3

输出样例#:

no

find

find

题目来源:

DreamJudge 1477

 

题目解析:通过分析题目我们可以发现,这道题有一个特点就是,数的集合在不断的改变。如果我们用先排序再二分的方法就会遇到困难,因为加入新的数的时候我们需要去移动多次数组,才能将数插入进去,最坏情况每次插入都是O(n)的复杂度,这是无法接受的。当然也不是说就不能用这样的方法来解决了,可以用离线的方法来解决这个问题,但是这样做太复杂,不适合在考试中使用。那么我们考虑用map来解决这个问题。

 

参考代码

 

  1. #include   
  2. using namespace std;  
  3.   
  4. int main(){  
  5.     int n,q,x;  
  6.     map<intint> M;//定义一个map映射  
  7.     scanf("%d", &n);  
  8.     for (int i = 0; i < n; i++) {  
  9.         scanf("%d", &x);  
  10.         M[x]++;//记录集合中x有多少个  
  11.     }  
  12.     scanf("%d", &q);  
  13.     for (int i = 0; i < q; i++) {  
  14.         scanf("%d", &x);  
  15.         if (M[x] == 0) {//如果x的个数为0  
  16.             printf("no\n");  
  17.             M[x]++;//将x加入到集合中  
  18.         }  
  19.         else printf("find\n");  
  20.     }  
  21.     return 0;  

 

看了上面的代码,是不是发现用map来解决问题真是超级简单。所以学会灵活使用map将能极大的拉近你和大佬之间的距离,我们一起来学map吧!

 

当然不是说二分查找就没用了,我们也需要了解二分查找的原理,只不过。二分的前提是单调性,只要满足单调性就可以二分,不论是单个元素还是连续区间。下面我们也给出一个基本的二分查找代码,供大家参考。

 

  1. #include   
  2. using namespace std;  
  3.   
  4. int a[10005];  
  5. int main(){  
  6.     int n,x;  
  7.     scanf("%d", &n);//输入n个数  
  8.     for (int i = 1; i <= n; i++) {  
  9.         scanf("%d", &a[i]);  
  10.     }  
  11.     sort(a+1, a+1+n);//排序保持单调性  
  12.     scanf("%d", &x);//要查找的数x  
  13.     int l = 1, r = n;  
  14.     while (l < r) {  
  15.         int mid = (l + r) / 2;  
  16.         if (a[mid] == x) {  
  17.             printf("find\n");  
  18.             return 0;  
  19.         }  
  20.         if (a[mid] > x) {//如果x比中间数小  
  21.             r = mid - 1;//说明在左区间  
  22.         }  
  23.         else l = mid + 1;//否则在右区间内  
  24.     }  
  25.     printf("not find\n");  
  26.     return 0;  
  27. }  

 

 

 

练习题目

DreamJudge 1177 查找学生信息

DreamJudge 1388 查找1

DreamJudge 1387 查找 - 北邮

DreamJudge 1383 查找第K小数

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.8 贪心类问题

贪心类问题是很常见的考点,贪心算法更重要的是一种贪心的思想,它追求的是当前最优解,从而得到全局最优解。贪心类问题基本上算是必考题型之一,它能更好的考察出学生的思维能力以及对问题的分析能力,很多学校的出题人都非常爱出贪心类的题目。

 

贪心算法的定义:

贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。

贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

 

贪心可以很简单,简单到让所有人一眼就能看出来该怎么做。贪心也可以很难,难到让你没办法去证明这样贪心的正确性。所以要想解决贪心这类问题,主要还是看你的悟性,看你对题目的分析能力如何,下面我们举例说明。

 

例子1:地上有3张纸币,分别是5元、1元和10元,问你只能拿一张,最多能拿多少钱?

解析:很明显,10元。

例子2:地上有n张纸币,有1元的,有5元的,还要10元的,问你只能拿一张,最多能拿多少钱?

解析:很明显,还是10元。

例子3:地上有很多纸币,有a张1元的,有b张5元的,还要c张10元的,问你从中拿x张,最多能拿多少钱?

解析:大家应该都能想到,肯定是优先拿10元的,如果10元的拿完了,再拿5元的,最后才会拿1元的。这就是贪心的思想,所以贪心其实是很容易想到的。

例子4:有n个整数构成的集合,现在从中拿出x个数来,问他们的和最大能是多少?

解析:相信大家都能想到,优先拿大的,从大到小一个个拿,这样组成的和最大。那么在解决这个问题之前,我们需要先排序,从大到小的排好序,然后将前x个数的和累加起来就是答案。

 

从上面几个例子中,相信大家对贪心已经有了初步的了解。我们使用贪心的时候,往往需要先按照某个特性先排好序,也就是说贪心一般和sort一起使用。

 

喝饮料

题目描述:

商店里有n中饮料,第i种饮料有mi毫升,价格为wi。

小明现在手里有x元,他想吃尽量多的饮料,于是向你寻求帮助,怎么样买才能吃的最多。

请注意,每一种饮料都可以只买一部分。

输入描述:

有多组测试数据。

第一行输入两个非负整数x和n。

接下来n行,每行输入两个整数,分别为mi和wi。

所有数据都不大于1000。

x和n都为-1时程序结束。

输出描述:

请输出小明最多能喝到多少毫升的饮料,结果保留三位小数。

输入样例#:

233 6

6 1

23 66

32 23

66 66

1 5

8 5

-1 -1

输出样例#:

136.000

题目来源:

DreamJudge 1478

 

题目解析:通过分析之后我们可以发现,小明想要喝尽量多的饮料的话,肯定优先选择性价比最高的饮料喝,也就是说1毫升的价格最低的饮料先喝,那么我们就需要去比较,每种饮料1毫升的价格是多少。然后按照这个单价从低到高依次排序,然后一个一个往后喝,这样可以保证小明能喝到最多的饮料。

 

参考代码

  1. #include   
  2. using namespace std;  
  3.   
  4. struct node {  
  5.     double w, m;  
  6. }p[1005];  
  7. bool cmp(node a, node b) {  
  8.     //按照每毫升的价格从低到高排序  
  9.     return a.w/a.m < b.w/b.m;  
  10. }  
  11. int main(){  
  12.     int n,x;  
  13.     while (scanf("%d%d", &x, &n) != EOF) {  
  14.         if (x == -1 && n == -1) break;  
  15.         for (int i = 1; i <= n; i++) {  
  16.             scanf("%lf%lf", &p[i].m, &p[i].w);  
  17.         }  
  18.         sort(p+1, p+1+n, cmp);  
  19.         double ans = 0;  
  20.         for (int i = 1; i <= n; i++) {  
  21.             if (x >= p[i].w) {//如果剩余的钱能全买  
  22.                 ans += p[i].m;  
  23.                 x -= p[i].w;  
  24.             }  
  25.             else {//如果剩余的钱买不完这种饮料  
  26.                 ans += (p[i].m*x/p[i].w);  
  27.                 break;//到这里x已经为0了  
  28.             }  
  29.         }  
  30.         printf("%.3lf\n", ans);  
  31.     }  
  32.     return 0;  

 

 

解题的通用步骤

1、建立数学模型来描述问题;

2、把求解的问题分成若干个子问题;

3、对每一子问题求解,得到子问题的局部最优解;

4、把子问题的局部最优解合成原来问题的一个解。

 

题型总结

贪心问题在很多机试难度低的学校,可以成为压轴题,也就是通过人数最少的题目。在机试难度高的学校也是中等难度及以上的题目,为什么明明贪心看起来这么容易的题目,却成为大多数学生过不去的坎呢?原因有二,一是很多同学根本就没有想到这个题目应该用贪心算法,没能将题目抽象成数学模型来分析,简单说就是没有读懂题目隐藏的意思。二是读懂题了,知道应该是贪心算法解这个题目,但是排序的特征点却没有找准,因为不是所有题目都是这么明显的看出来从小到大排序,有的题目可能隐藏的更深,但是这种难度的贪心不常见。所以机试中的贪心题,你要你反应过来这是一个贪心,99%的情况下都能解决。

 

 

 

练习题目

DreamJudge 1307 组队刷题

DreamJudge 1347 To Fill or Not to Fill

 

 

 

 

2.9 链表类问题

链表类问题属于选读章节,对于使用OJ测评的院校的同学来说,这类问题可以用数组来实现,没有必要用链表去实现,写起来慢不说,还容易出错,所以我们一般都直接用数组来实现,反正最后OJ能AC就行,建议这类同学跳过本节或仅做了解即可。但是对于非OJ测评的院校来说,链表类问题可以说是必考的题型。

一般来说有以下三种常见考点

  1. 猴子报数

解析:循环链表建立之后,按照题意删除节点。

  1. 两个有序链表合并为一个

解析:这个和两个有序数组合并为一个有序数组原理一样。

  1. 链表排序

解析:使用冒泡排序进行链表排序,因为冒泡排序是相邻两个元素进行比较交换,适合链表。

 

猴子报数

题目描述:

n个猴子围坐一圈并按照顺时针方向从1到n编号,从第s个猴子开始进行1到m的报数,报数到第m的猴子退出报数,从紧挨它的下一个猴子重新开始1到m的报数,如此进行下去知道所有的猴子都退出为止。求给出这n个猴子的退出的顺序表。

输入描述:

有做组测试数据.每一组数据有两行,第一行输入n(表示猴子的总数最多为100)第二行输入数据s(从第s个猴子开始报数)和数据m(第m个猴子退出报数).当输入0 0 0时表示程序结束.

输出描述:

每组数据的输出结果为一行,中间用逗号间隔。

输入样例#:

10

2 5

5

2 3

0

0 0

输出样例#:

6,1,7,3,10,9,2,5,8,4

4,2,1,3,5

题目来源:

DreamJudge 1081

 

题目解析:我们需要创建一个首尾相连的循环链表,然后先走s步,再开始循环遍历链表,每走m步删除一个节点,知道链表中只能下一个节点时结束循环。只能一个节点的判断条件是,它的下一个指针指向的是它,说明它自循环了。

 

  1. #include   
  2. #include   
  3. struct node {  
  4.     int num;  
  5.     struct node *next;  
  6. };  
  7. int n, s, m;  
  8. //创建循环链表  
  9. struct node* create() {  
  10.     struct node *head, *now, *pre;  
  11.     for (int i = 1; i <= n; i++) {  
  12.         now = (struct node *)malloc(sizeof(node));  
  13.         if (i == 1) {//第一个节点需要处理  
  14.             head = now;//头结点指向第一个节点  
  15.             pre = now;//上一个节点也指向它  
  16.         }  
  17.         now->num = i;  
  18.         now->next = head;  
  19.         pre->next = now;  
  20.         pre = now;//将当然节点作为上一个节点  
  21.     }  
  22.     return head;  
  23. };  
  24. //按照题目要求输出  
  25. void print(struct node *head) {  
  26.     struct node *p;  
  27.     p = head;  
  28.     while (s--) {//先走s步  
  29.         p = p->next;  
  30.     }  
  31.     int i = 1;  
  32.     while (p != NULL) {  
  33.         if (p == p->next) {//只剩最后一个  
  34.             printf("%d\n", p->num);  
  35.             break;  
  36.         }//这里有个小技巧 我们遍历到满足条件的上一个  
  37.         if ((i+1) % (m-1) == 0) {//然后输出下一个  
  38.             printf("%d,", p->next->num);//这样方便删除  
  39.             p->next = p->next->next;//删除下一个节点  
  40.         }  
  41.         p = p->next;  
  42.         i++;  
  43.     }  
  44. }  
  45. int main(){  
  46.     while (scanf("%d%d%d", &n, &s, &m) != EOF) {  
  47.         if (n==0&&s==0&&m==0) break;   
  48.         struct node *head;  
  49.         head = create();  
  50.         print(head);  
  51.     }  
  52.     return 0;  
  53. }  

 

练习题目

DreamJudge 1015 单链表

DreamJudge 1018 击鼓传花

DreamJudge 1025 合并链表

DreamJudge 1405 遍历链表

 

第三章 数学

3.1 同模余定理

3.2 最大公约数(GCD)

3.3 最小公倍数(LCM)

3.4 斐波那契数列

3.5 素数判定

3.6 素数筛选

3.7 分解素因数

3.8 二分快速幂

3.9 常见数学公式总结

3.10 规律神器OEIS

第四章 高精度问题

4.1 Python解法

4.2 Java解法

4.3 C/C++解法

第五章 数据结构

5.1 栈的应用

5.2 哈夫曼树

5.3 二叉树

5.4 二叉排序树

5.5 hash算法

5.6 前缀树

第六章 搜索

6.1 暴力枚举

6.2 广度优先搜索(BFS)

6.3 递归及其应用

6.4 深度优先搜索(DFS)

6.5 搜索剪枝技巧

6.6终极偏分技巧

第七章 图论

7.1 理论基础

7.2 图的存储

7.3 并查集

7.4 最小生成树问题

7.5 最短路径问题

7.6 拓扑排序

7.7 打印路径类问题

第八章 动态规划

8.1 递推求解

8.2最大子段和

8.3 最长上升子序列(LIS)

8.4 最长公共子序列(LCS)

8.5 背包类问题

8.6 记忆化搜索

8.7 字符串相关的动态规划

 

你可能感兴趣的:(计算机考研)