《冬训周报六》
写在前面
之前看了一节蓝桥云课,跟着记录了一些常考知识点,上期周报也提到了一部分,这期也是补充,再加点些别的。
杂题
蓝桥上的老师是这样讲的
我觉得吧这就是模拟,没有什么模板,就是根据题目表述2筛选提取关键要素,按需求书写代码,解决实际问题,通俗点就是“照葫芦画瓢”。
模拟算法一般都是些比较基础的题目,或被定义为签到题(?),不过我觉得模拟题也分难易,对于一些题目复杂难理解的就容易做错,有些时候还有很坑的数据让WA掉QAQ,所以,在此提醒自己:
模拟题要谨慎!!!
模拟题要谨慎!!
模拟题要谨慎!
(重要的事情说三遍)不过就像蓝桥老师说得那样,这比较考验思维和编码能力,需要多做题来提高(莫名想到高三时期刷题的状态)。
BFS搜索和DFS搜索
也就是暴力搜索。
很多时候看到题第一眼就是想暴力搜索,但是又想到自己还是学了些其他的算法还是要多熟悉下就又没那样写了。
暴力搜索的话,算法会比较简单,可以方便看懂代码,也可以解决大部分的问题,但执行效率低,碰到那种对时间有要求的题目多半都要TLE,但是有些时候也可以通过“剪枝”来提高编程效率。
所谓“剪枝”,就是找出问题的枚举范围和约束条件,枚举范围就是说分析问题所涉及的各种情况,通俗点讲可以理解为数学上的分类讨论,而找出约束条件,就是分析问题的解满足的条件。
说了这么多还是讲回dfs和bfs吧,其实我自己也不是能保证自己对这两个算法和熟练,至少有些时候想暴力解决也因为摸不着头脑而无从下手,大佬们认为这是基础中的基础,所以那我至少也应该好好记录一下自己对于这两算法的熟悉程度,或者说借助写周报好好总结一下,毕竟以前自己是没有这个习惯的,所以有些时候总觉得以前的暴力算法总是东拼西凑出来的一样。
DFS算法
简单陈述一下思想,就是一直往深处走,直到找到解或者走不下去位置。
做法:
使用栈保存未被检测的结点,结点按照深度优先的次序被访问并依次被压入栈中,并以相反的次序出栈进行新的检测。
模板:
#include<bits/stdc++.h> using namespace std; const int maxn=100; bool vst[maxn][maxn]; // 访问标记 int map[maxn][maxn]; // 坐标范围 int dir[4][2]={0,1,0,-1,1,0,-1,0}; // 方向向量,(x,y)周围的四个方向 bool CheckEdge(int x,int y) // 边界条件和约束条件的判断 { if(!vst[x][y] && ...) // 满足条件 return 1; else // 与约束条件冲突 return 0; } void dfs(int x,int y) { vst[x][y]=1; // 标记该节点被访问过 if(map[x][y]==G) // 出现目标G { ...... // 做相应处理 return; } for(int i=0;i<4;i++) { if(CheckEdge(x+dir[i][0],y+dir[i][1])) // 按照规则生成下一个节点 dfs(x+dir[i][0],y+dir[i][1]); } return; // 没有下层搜索节点,回溯 } int main() { ...... return 0; }
BFS算法
FIFO: First in, First out
代表先进的数据先出 ,后进的数据后出。
做法:
使用栈保存未被检测的结点,结点按照深度优先的次序被访问并依次被压入栈中,并以相反的次序出栈进行新的检测。
模板:
#include<cstdio> #include<cstring> #include<queue> #include<algorithm> using namespace std; const int maxn=100; bool vst[maxn][maxn]; // 访问标记 int dir[4][2]={0,1,0,-1,1,0,-1,0}; // 方向向量 struct State // BFS 队列中的状态数据结构 { int x,y; // 坐标位置 int Step_Counter; // 搜索步数统计器 }; State a[maxn]; bool CheckState(State s) // 约束条件检验 { if(!vst[s.x][s.y] && ...) // 满足条件 return 1; else // 约束条件冲突 return 0; } void bfs(State st) { queue <State> q; // BFS 队列 State now,next; // 定义2 个状态,当前和下一个 st.Step_Counter=0; // 计数器清零 q.push(st); // 入队 vst[st.x][st.y]=1; // 访问标记 while(!q.empty()) { now=q.front(); // 取队首元素进行扩展 if(now==G) // 出现目标态,此时为Step_Counter 的最小值,可以退出即可 { ...... // 做相关处理 return; } for(int i=0;i<4;i++) { next.x=now.x+dir[i][0]; // 按照规则生成 下一个状态 next.y=now.y+dir[i][1]; next.Step_Counter=now.Step_Counter+1; // 计数器加1 if(CheckState(next)) // 如果状态满足约束条件则入队 { q.push(next); vst[next.x][next.y]=1; //访问标记 } } q.pop(); // 队首元素出队 } return; } int main() { ...... return 0; }
动态规划
之前也说过,我觉得贪心和动态规划相似,但是并不相同。
动态规划能解决的问题:
1) 问题具有最优子结构性质。如果问题的最优解所包含的 子问题的解也是最优的,我们就称该问题具有最优子结 构性质。
2) 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。
什么是最优子结构?→最优子结构(optimal substructure)_五道口纳什的博客-CSDN博客_最优子结构,什么是最优子结构、如何判定 DP 数组的遍历方向 - 知乎 (zhihu.com)
动态规划的一般解题思路:
1,将原问题分解为子问题
- 把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决。
- 子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。
2,确定状态
- 在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。
- 所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 在数字三角形的例子里,一共有N×(N+1)/2个数字,所以这个问题的状态空间里一共就有N×(N+1)/2个状态。
3,确定状态转移方程
- 定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。
如经典例题数字三角形(POJ1163)_aiLMengi000的博客-CSDN博客
中的动态转移方程
分块
这里是学长讲得分块思想,分块思想 - SMU XCPC Wiki (smu-xcpc.github.io)
听的时候还是能听懂,但还不太熟悉,在此记录一下,以后好回顾。
Python 光速入门教程
acm可能需要学一下python,Python 光速入门教程 - SMU XCPC Wiki (smu-xcpc.github.io)