文章内容概要
本次文章将会讲算法中的搜索。这几个内容在蓝桥杯中非常的常考,建议大家认真阅读。
下期将会为大家讲解图论相关的知识,等基础算法篇更新完之后,博主会每周更新一些自己做过的比较好的算法题和他们的技巧。
搜索
搜索也叫做暴搜,在未优化前就是通过穷举所有情况来找到最优解
搜索一般分为深度优先搜索和宽度优先搜索
一般用到的优化方法是:回溯和剪枝
回溯:在搜索过程中,遇到走不通或者走到底的情况时,就回头
剪枝:在搜索过程中,剪掉重复出现或者不是最优解的分支
用的数不重复的用排列组合思想去分析题(像eg:高中的C和A类型的题)
深度优先搜索-DFS(递归型枚举)
实现方法:(用全局变量标记每一步干了啥)+回溯来实现dfs
辅助理解:画决策树
递归型枚举这类题的数据范围都很小–可以当做一个题眼
洛谷 B3622 枚举⼦集(递归实现指数型枚举)
洛⾕ P1706 全排列问题
洛谷 B3623 枚举排列(递归实现排列型枚举)
1.例题: 洛谷 B3622 枚举⼦集(递归实现指数型枚举)
#include <bits/stdc++.h>
using namespace std;
int n;
string path;//全局变量,记录递归过程中,每一步的决策
void dfs(int pos)
{
if(pos>n)
{
cout<<path<<endl;
return;
}
//不选
path+='N';
dfs(pos+1);
path.pop_back();//回溯,一般在这个eg:dfs(pos+1)后面会用
//其他类型的数据的话,一般用vector存,好尾删
//选
略
int main()
{
cin>>n;
dfs(1);
return 0;
}
2.排列枚举
例题: 洛谷 B3623 枚举排列(递归实现排列型枚举)
洛⾕ P1706 全排列问题
都需要用到 bool st[N]去标记哪些已经选过了
一般会在回溯后面加上st[i]=false(表示选过了);//这是剪枝
剪枝与优化(dfs的)
洛谷 P10483 ⼩猫爬⼭
洛谷 P1464 Function
在dfs中,有几种常见的剪枝方法:
1.排除等效冗余:
如果在搜索过程中,通过某⼀个节点往下的若⼲分⽀中,存在最终结果等效的分⽀,那么就只需要搜索其中⼀条分⽀。
(比如:组合中的12和21)
2.可行性剪枝:
如果在搜索过程中,发现有⼀条分⽀是⽆论如何都拿不到最终解,此时就可以放弃这个分⽀,转⽽搜索其它的分⽀。
(比如:组合中的11)
3.最优化剪枝:(在找最优解时会用)
如果在搜索过程中,发现某⼀个分⽀已经超过当前已经搜索过的最优解,那么这个分⽀往后的搜索,必定不会拿到最优解。此时应该停⽌搜索,转⽽搜索其它情况。
4.优化搜索顺序:(在找最优解时会用)
在有些搜索问题中,搜索顺序是不影响最终结果的
此时,应当先选择⼀个搜索分⽀规模较⼩的搜索顺序,快速拿到⼀个最优解之后,⽤最优性剪枝剪掉别的分⽀。
例题: 洛谷 P10483 ⼩猫爬⼭
5.记忆化搜索:(有非常多完全相同的子问题时用此)
(可以通过增加形参来让子问题变得相同)
记录每⼀个状态的搜索结果,当下⼀次搜索到这个状态时,直接找到之前记录过的搜索结果。这也解决了以前遇到大量重复运算不能用递归的场景
例题: 洛谷 P1464 Function
记忆化搜索的注意事项:
1.备忘录中不能一开始就出现递归过程中有可能出现的值
2.递归返回的时候,先把值先存到备忘录里面
3.递归的时候,先往备忘录里面瞅一瞅(不要用成还没初始化的值了)
温馨提示:有些题对剪枝的位置也要要求(可从树状图看出)
例题:洛谷 P1025 [NOIP2001 提⾼组] 数的划分
宽度优先搜索(BFS)
BFS常用来解决边权为1的最短路问题
eg:在二维中至少走多少步才能到…
bfs题在写代码的时候:(dfs才是递归)
常用 queue<pair<int,int>>q来存坐标
表示走了多少步的int dist[N][N];
在while(q.size())里面去循环
多源BFS
当问题中存在多个起点⽽不是单⼀起点时,这时的最短路问题就是多源最短路问题。
在多源最短路问题中,边权为1的话就可以用多源BFS
把这些源点汇聚在⼀起,当成⼀个"超级源点"就变成了单源BFS
即 1.初始化的时候,把所有的源点都加⼊到队列⾥⾯;
2. 然后正常执⾏ bfs 的逻辑即可。
例题:牛客网 矩阵距离
01 BFS
感觉跟背包那的01问题差距还是大
这个01BFS是"走路"问题
在BFS过程中,把边权为0的扩展出来的点放在队首,把边权为1的扩展出来的点放到队尾(核心思想)
01BFS相较于其他BFS,如果遇到已经遍历过的结点,有可能会找到一条更优的路径
(上面的核心思想体现了这个)
例题 洛谷 P4554 ⼩明的游戏
Floodfill问题
本质是在寻找具有相同性质的联通块
洛谷 P1596 [USACO10OCT] Lake Counting S
例题: 洛谷 P1596 [USACO10OCT] Lake Counting S
其中的主要代码:
dx[] dy[]是可以走的那几个方向
//给联通的地方打上标记
void dfs(int i, int j)
{
st[i][j] =true;
for(int k = 0;k<0;k++)
{
int x = i + dx[k], y = j + dy[k];
if(x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] == 'W' && st[x][y] == false)
dfs(x,y)
}
}
int main()
{
...
if(a[i][j]=='W'&&st[i][j] ==false)
{
ret++;
dfs(i,j);
}
}
一些比较杂但是做题会用到的知识
有时会用到大坐标的方法:
例题:洛谷 P1784 数独
eg: 本为a[i][j],然后搞一个b[i/3][j/3][num] = true;(举例:i,j为9)
表示在a中的数num在[i/3][j/3]这个3x3方格中
如果一个坐标会一变多一直这样的话
可以用eg:queue<pair<int,int>> q;这些来存坐标
eg: 洛谷 P1443 ⻢的遍历
二维坐标转一维的方法:(二维的下标最好从0开始)
可以将nxm的矩阵坐标(x,y),映射成一个数pos,可以起到空间优化的效果
公式: pos = x*m+y;
x =pos/m;
y = pos%m;
常用思路:
正难则反
例题: 牛客网 矩阵距离
在此题表现为:
如果针对一个点,直接去找最近的1,那么就需要对所有0来一次bfs,但是时间复杂度太大了
因为去想从1向外扩展,每遍历到0就更新一下最短举例,这样就只用一次bfs
消消乐思想:
如果要标记外围同数据(eg:都为0)但是这些数据又很分散的话,解决这个的方法:
我们可以把整个矩阵的外围包上⼀层0 ,这样只⽤从(0,0)位置开始搜即可
例题:洛谷 P1162 填涂颜⾊
字符映射成连续数字的方法:
小写字母:0-25 ch-'a'
大写字母:26-51 ch-'A'+26
数字: 52-61 ch-'0'+52
应用eg;字典树那的path
股票问题中的重要结论:
任意一笔跨天的交易,都可以转化成连续的"某天买,隔天卖"的形式
例题:洛谷 P5662 [CSP-J2019] 纪念品
如果需要让列排序的话,必须要先转换成行,然后用sort
例题:洛谷 P5322 [BJOI2019] 排兵布阵
for(int k = i;k<j;k++)这里循环j-i次
处理环形问题的常用技巧:
倍增--即复写(这里的倍增不是指前面的倍增算法)
例题: 牛客网 丢手绢