模拟,贪心,枚举
文件第二次被毁之后我乖乖的学会了该弃坑就弃坑,Typora,没想到你是这样的编辑软件,写了3500+说没就没了。博客园大爱,还给自动保存。
进入正题
###二、模拟枚举贪心
模拟,枚举,贪心。这三者算是从语言基础向算法设计的一个过渡,刚刚完成了C++语言基础学习的各位也算是正式步入了算法竞赛的殿堂,那么下面就由笔者带领大家向梦想更深处前行。——题记
####本章简介
模拟、枚举和贪心算是比较基础的知识点,但是显然他们在算法竞赛中占有很重要的无可替代的地位。题目虽然基础但是并不是没有难度,就拿模拟来说,可以简单如3n+1,亦可繁琐如麻将。本章只是带萌新们熟悉这几种问题,有一个初步的了解,要想提高实战能力那必然要靠自己长期训练。
个人认为,前两者而言问题更偏向于考查选手的代码功底。而贪心就稍稍有点算法的感觉。贪心也就算是这三者中最有难度的一个了吧,当然这个在算法世界里面已经算是简单的了,贪心的难度主要体现在贪心正确性的证明,当然也有凭感觉凭信仰或者反正没辙了不如写个贪心的选手,这时候没有贪心证明的桎梏,那就基本只考很简单的代码功底了。在本章,笔者的目的就是让各位简单的了解一下这几种问题的特征,能够在将来见到这种问题时能够灵活的处理,成功AC。
那么接下来笔者会分三节分别介绍这三类问题,在每一节中都遵循以下流程:1.简单介绍此类问题,描述特征。2.提供笔者解题思路,仅供参考。3.例题与解析并附代码,帮助理解。4.课后无解析习题,巩固提高。
那么不多说了,开始教程。
####模拟
我们姑且按照题目的顺序来,先来模拟:
模拟,就笔者个人的理解,就是将人的思路转化为清晰的程序语言的过程。这类题的典型特征嘛,倒是很容易辨别,只要按照题目所给的信息,怎么想的怎么写就可以在规定的时间空间限制内完成题目交代的任务。当然题目难度也是有高有低的。
下面我来提供我个人的解题思路:
1.审准题,理解题意。在文化课的学习中想必大家也都经历过一道题做了半天没做出来,到最后发现自己读错题或者漏看条件,在OI赛场上一样如此,而且尤为严重,辛辛苦苦写了代码调试好了,到最后发现自己写的完全不合题意,这是赛场上一定要避免的。
2.手动解决,理清思路。在理解题意之后可以试着自己手动推一推样例,看看样例为什么是这个结果,也可以自己找一点例子来尝试一下,这样先理清思路,利于下一步写代码。
3.考虑特例,虽然这种东西在模拟中并不常见,但是养成全面细致的思维方式也是很重要的,毕竟这种东西以后会很常见,经常坑分。
4.写代码吧!认真一点,一气呵成别浪费时间。
5.调试!确保万无一失,自己多造点样例试试。
好了,以上就是笔者解题的一般性思路了,仅供参考。
下面是例题咯~
#####例题一:神奇的幻方(NOIP2015 Day1 T1)
[题目描述 Description]
幻方是一种很神奇的N∗N矩阵:它由数字 1,2,3, … … ,N∗N构成,且每行、每列及两条对角线上的数字之和都相同。
当N为奇数时,我们可以通过以下方法构建一个幻方:
首先将 1写在第一行的中间。之后,按如下方式从小到大依次填写每个数(K= 2,3, … ,N∗N ):
1.若 (K−1)在第一行但不在最后一列,则将 填在最后一行,(K−1)所在列的右一列;
2.若 (K−1)在最后一列但不在第一行,则将填在第一列,( K−1)所在行的上一行;
3.若 ( K−1)在第一行最后一列,则将填在(K −1)的正下方;
4.若 (K−1)既不在第一行,也不在最后一列,如果( K−1)的右上方还未填数,
则将 K填在( K−1)的右上方,否则将填在( K− 1)的正下方。
现给定N,请按上述方法构造N∗N的幻方。
[输入描述 Input Description]
输入文件只有一行,包含一个整数,即幻方的大小。
[输出描述 Output Description]
输出文件包含N行,每行N个整数,即按上述方法构造出的N∗N的幻方。相邻两个整数之间用单个空格隔开。
[样例输入 Sample Input]
3
[样例输出 Sample Output]
8 1 6
3 5 7
4 9 2
数据范围及提示 Data Size & Hint
对于 100%的数据,1 ≤ N ≤ 39且为奇数。
[题目分析]
首先这绝对是模拟,毕竟它连怎么操作都告诉你了,那么这就很没意思了。那么我还是按照题意来走一下模拟的形式。
所以建议大家尽量先自己写,实在不行再看下面的分析与代码。
方法一:模拟嘛,完全按照他说的来咯。首先找个二维数组当幻方,然后第一行正中间填个1,然后开始按照规则行事。那么我们按照人脑最直接的方法:假如我现在要知道2应该填在哪,那么我可以找一找1在哪,遍历整个cube定位我们的1,然后决定2填在哪里,以此类推。那么我们就有了下面的代码。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 int cube[45][45];//幻方,稍稍开大一点总是好的; 6 int main(void) 7 { 8 int n; 9 cin>>n; 10 memset(cube,0,sizeof(cube));//虽然全局变量初始是0,但是memset一下总是好的 11 for(int num=1;num<=n*n;num++)//当前填入的数字 12 { 13 if(num==1)cube[1][n/2+1]=1; 14 else 15 { 16 for(int j=1;j<=n;j++) 17 for(int k=1;k<=n;k++)//找到上一个数k-1在哪 18 { 19 if(cube[j][k]==num-1) 20 { 21 if(j==1 && k!=n)cube[n][k+1]=num; 22 else if(k==n && j!=1)cube[j-1][1]=num; 23 else if(j==1 && k==n)cube[j+1][k]=num; 24 else if(j!=1 && k!=n) 25 { 26 if(!cube[j-1][k+1])cube[j-1][k+1]=num; 27 else cube[j+1][k]=num; 28 } 29 } 30 } 31 } 32 } 33 for(int i=1;i<=n;i++) 34 { 35 for(int j=1;j<=n;j++) 36 { 37 cout<<cube[i][j]<<" "; 38 } 39 cout<<endl; 40 } 41 return 0; 42 }
当然这个方法写出的代码并不精良,效率偏低。下面我们来分析一下个中缘由。顺便各位也体会一下怎样提高代码效率。
方法二:
正如刚刚所言,上一份代码效率是很低的。具体低在哪呢?我们每一次寻找数字k-1的时候都需要遍历一遍cube这样即使我们的一个数被查过20次,我们还会坚毅的再那它排查一遍。所以效率很低,那么我们干脆采用递推的形式,填完一个数k-1,直接推出k在哪然后填入之后以此类推,这样就能极大的提高程序效率。下面是利用函数的形式写的代码,仅供参考。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 int cube[45][45]; 6 int n; 7 void make(int num,int x,int y) 8 { 9 if(num==n*n)return; 10 if(x==1 && y!=n) 11 { 12 cube[n][y+1]=num+1; 13 make(num+1,n,y+1); 14 } 15 else if(y==n && x!=1) 16 { 17 cube[x-1][1]=num+1; 18 make(num+1,x-1,1); 19 } 20 else if(x==1 && y==n) 21 { 22 cube[x+1][y]=num+1; 23 make(num+1,x+1,y); 24 } 25 else if(x!=1 && y!=n) 26 { 27 if(!cube[x-1][y+1]) 28 { 29 cube[x-1][y+1]=num+1; 30 make(num+1,x-1,y+1); 31 } 32 else 33 { 34 cube[x+1][y]=num+1; 35 make(num+1,x+1,y); 36 } 37 } 38 } 39 int main(void) 40 { 41 cin>>n; 42 memset(cube,0,sizeof(cube)); 43 cube[1][n/2+1]=1; 44 make(1,1,n/2+1); 45 for(int i=1;i<=n;i++) 46 { 47 for(int j=1;j<=n;j++) 48 { 49 cout<<cube[i][j]<<" "; 50 } 51 cout<<endl; 52 } 53 return 0; 54 }
好了,这个水题就是这样了。