第一章 准备篇
1.1 OJ结果:
Accepted :通过用例
Time Limit Exceed :超时
Runtime Error: 程序非法访问内存或未处理异常
Memory Limit Exceeded: 内存超限
Presentation Error: 输出的内容对的,但是格式不符合
Output Limit Exceeded : 输出过多内容
Compile Error 未能编译
1.2 运行时间
对于一个限制时间为1秒的题目,我们要估计算法的复杂度。并将数值可能最大的值带入其中
如:n²的复杂度,若n≤1000,则平方为 1000 000 .
在1秒的限制时间内完成:
1000000 游刃有余
10000000 勉勉强强
100000000 几乎不可能
第二章 初级篇
2.1 穷举搜索
2.1.1 栈
#include<stack> 可以直接调用c++自带的栈库
stack<int> s 使用模板类来进行声明。
s.push(3) 向栈中推进int 3.
s.pop()是推出。但是并不返回值
s.top()才是返回值。
2.1.2 队列
#include<queue>
queue<int> 可以声明
需要注意的是,queue.front()才是返回队首的值,而pop也是将其推出。
2.1.3 深度优先搜索DFS
下面这道题是一个判断农田水洼数目的题。
由于最近的降雨,水汇集在Farmer John's田地的不同地方,其由N×M(1 <= N <= 100; 1 <= M <= 100)的正方形矩形表示。每个方块包含水('W')或旱地('。')。农夫约翰想弄清楚他的田地里有多少个池塘。池塘是一组连接的正方形,其中有水,其中一个正方形被认为与其所有八个邻居相邻。
10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
- [√] 这道题用了深度搜索。也就是,从一个点开始判断,判断其是否是水洼,如果是水洼,则判断其周围的8个点是不是水洼。只要有一个是水洼,就再以这个点为基准点,进行递归再次搜索其周围的8个点。
最终结果是对每个点至多会有8次循环,对于搜索到的点,都会对其周围八个点进行搜索,进行8次循环。因此,复杂度小于O(8NM)。
并且,搜索过的水洼点会被置为’.’。在外部循环就不会被再次遍历。
思路就是,对整个农田进行遍历,遇到一个水洼就进行深度搜索,直到将其相邻水洼都置为’.’。外部循环的每次返回相当于一个池塘被填平。我们只要记录池塘被填平的次数就可以了。
#include<stdio.h>
int N,M;
int Max_N=100;
int Max_M=100;
char field[100][100];//田地的大小
void DFS(int x,int y){
field[x][y]='.';
for(int x_=-1;x_<=1;x_++){
for(int y_=-1;y_<=1;y_++){//每个点的上下左右
int ix = x+x_;
int iy = y+y_;
if(ix<N&&iy<M&&field[ix][iy]=='W'&&ix>=0&&iy>=0)DFS(ix,iy);//只有水洼点才有继续遍历的必要
}
}
return;
}
int main(){
while(scanf("%d %d",&N,&M)!=EOF){
for(int i=0;i<N;i++){
getchar();
for(int j=0;j<M;j++){
scanf("%c",&field[i][j]);
}
}
//获取数据
int count=0;
for(int i=0;i<N;i++){
for(int j=0;j<M;j++){
if(field[i][j]=='W'){//只有遇到水洼点,才进行深度遍历
DFS(i,j);
count++;
}
}
}
printf("%d\n",count);
}
return 0;
}
2.1.4 广度优先遍历
广度优先搜索就是优先查找和当前状态相似的状态,比如迷宫问题,寻找通向终点的最短路径。
从起点开始,记录到四个方向的距离(第一步是1),然后将这四个方向的坐标放入队列当中。第二步,从队列中取出一个方向的坐标,表示从这里开始,再找他的四个方向。并且这次记录的距离是从起点到它这里的距离(1)再加上1.将距离和坐标记录后,将新的四个方向放入队列。接下来,再从队列取出一个坐标,这是第一步的第二个方向。当队列为空时,说明没有可达点了。这时再看起点到终点的距离是多少。如果是初始化值如inf,则说明终点不可达。
与深度优先不同,深度优先是一直进行下去,直到“触底”后,再回到上一步的另一个分支。而广度优先则是在起点先把从起点的分支遍历一次,再遍历二级分支一次,直到没有分支可以遍历。
2.1.5 枚举
使用库中的next_permutation(start,start+n)可以将数组start生成n!个枚举。
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int perm2[10];
void permutation(int n){
for(int i=0;i<n;i++){
perm2[i]=i;
}
while(std::next_permutation(perm2,perm2+n)){//当next_permutation函数生成了所有枚举后,就会返回false。
for(int i=0;i<n;i++){
printf("%d ",perm2[i]);
}
printf("\n");
}
}
int main(){
//printf("11");
permutation(4);
}
但是要注意,似乎生成的n!不包括其原本顺序。
另外,也可以用一个类似深度优先遍历的方法来生成。
bool used[100]
int perm[100]
//这里生成 0--100的所有序列。
perm1(pos,n){
if(pos==n){//说明100个数字已经填满一个队列
打印队列
return;
}
for(int i=0;i<n;i++){
//这里n=100
if(used[i]==false){
perm[pos]=i;//在第pos个位置放数字i
used[i]=true;
perm1(pos+1,n);
used[i]=false;
}
}
return;//当这一个pos的可用数字全部用完了,就让其上一个Pos的值改变。再进行深度操作。
}
2.1.6 剪枝
上面所说的穷举法,可能会因为解空间过大而时间复杂度过高。在判断过程中,可能会有一些细节能让我们知道该路径可以提前结束。从而减少不必要的搜索活动。