首先明确回溯框架的使用范围
-
求解方案(分为两种)
1.求解方案的种数(楼梯,硬币找零)
2.求解具体的方案 (硬币找零的方案,八皇后,全排列,子集,组合,数独等)
-
求最优解(一般可以用dp优化或者有记录型参数sum>res return来剪枝)
如果非要回溯的话,要用备忘录来优化!
框架介绍
1.方案数类
//一般都是这样再添加一些东西
void dfs(int n){
//递归结束条件
if(...) {
res++;
return;
}
for(int i=0;i<n;i++){
//跳过不符合要求的
if(...) continue;
//做选择
dfs(...);
}
举个简单例子吧!
上楼梯的问题其实就是回溯问题(可以自己画出递归树,方便理解)
/*3089:爬楼梯
查看
提交
统计
提问
总时间限制:
1000ms
内存限制:
65536kB
描述
树老师爬楼梯,他可以每次走1级或者2级,输入楼梯的级数,求不同的走法数
例如:楼梯一共有3级,他可以每次都走一级,或者第一次走一级,第二次走两级
也可以第一次走两级,第二次走一级,一共3种方法。
输入
输入包含若干行,每行包含一个正整数N,代表楼梯级数,1 <= N <= 30
输出
不同的走法数,每一行输入对应一行输出
样例输入
5
8
10
样例输出
8
34
89
*/
#include <iostream>
using namespace std;
void back(int m,int &res){
//回溯条件,即到了楼底了
if(m==0){
// 方案++
res++;
return;
}
for(int i=1;i<=2;i++){
// 不符合条件
if(m-i<0) continue;
// 做选择
back(m-i,res);
}
}
int main(){
int m;
while(cin >> m){
int res = 0;
back(m,res);
cout<<res<<endl;
}
return 0;
}
值得注意的是这里dfs还需要注意,一般我们记录所有方案的话,就需要选择和撤销选择,来维护节点的属性!
2.具体方案类
举个简单例子就是全排列。
思考一下,选择之后不撤销的话就会对其他子问题的路径记录有影响!
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;
vector<vector<int> > res;
void dfs(vector<int> nums,vector<int>& track);
int main(){
vector<int> nums;
for(int i=1;i<=3;i++){
nums.push_back(i);
}
vector<int> track;
dfs(nums,track);
vector<vector<int> >::iterator iter;
for(iter=res.begin();iter!=res.end();iter++){
vector<int> in = *iter;
vector<int>::iterator iter2;
for(iter2=in.begin();iter2!=in.end();iter2++){
cout<<*iter2;
}
cout<<endl;
}
}
// nums为选择,track为路径
void dfs(vector<int> nums,vector<int>&track){
if(track.size()==nums.size()){
//将当前的路径加入到res
res.push_back(track);
return;
}
for(int i=0;i<nums.size();i++){
//用路径进行剪枝
if(count(track.begin(),track.end(),nums[i])) continue;
//选择
track.push_back(nums[i]);
dfs(nums,track);
//撤销
track.pop_back();
}
}
再来一个李白喝酒问题
题目描述:
李白街上走,提壶去买酒,遇店加一倍,见花喝一斗”,途中,遇见5次店,见了10此花,壶中原有2斗酒,最后刚好喝完酒,要求最后遇见的是花,求可能的情况有多少种?
题解:
#include <iostream>
#include <vector>
#include <list>
using namespace std;
void dfs(int jiu,int hua,int dian);
int res = 0;
int main(){
int jiu = 2;
int hua = 10;
int dian = 5;
dfs(jiu,hua,dian);
cout<<res;
}
void dfs(int jiu,int hua,int dian){
//结束条件
if(jiu==1&&hua==1&&dian==0){
res++;
return;
}
if(hua>0){
dfs(jiu-1,hua-1,dian);
}
if(dian>0){
dfs(jiu*2,hua,dian-1);
}
}
这是我目前总结的经验,后续遇见其他的还会补充的!不懂的可以问我这个小菜鸡!
21年2月5日添加
还有一种大家特别常见的就是迷宫问题了
例如:
蒜厂有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。
请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
输入格式
第一行是两个整数
W
和
H
,分别表示
x
方向和
y
方向瓷砖的数量。
W
和
H
都不超过
20
。
在接下来的
H
行中,每行包括
W
个字符。每个字符表示一块瓷砖的颜色,规则如下
1)'.':黑色的瓷砖;
2)'#':白色的瓷砖;
3)'@':黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
输出格式
输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。
Sample Input
6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
Sample Output
45
思路: 仍然使用回溯法,只是解决不同的问题我们得稍微变通一下下!
题解:
#include <iostream>
using namespace std;
// 初始化为1,脚下的也算
int res = 1;
int row,col;
char maps[25][25];
int visited[25][25];
void dfs(int x,int y){
// 不符合条件,回溯
if(maps[x][y]=='#') return;
// 越界回溯
if(visited[x][y]==1||x<0||y<0||x>=row||y>=col) return;
// 符合条件 res++ 继续递归
if(maps[x][y]=='.'){
res++;
visited[x][y]=1;
}
// 尝试每种情况
dfs(x-1,y);
dfs(x,y+1);
dfs(x+1,y);
dfs(x,y-1);
}
int main(){
cin >> col >> row;
int startx,starty;
for(int i=0;i<row;i++){
for(int j=0;j<col;j++){
cin >> maps[i][j];
// 记录初始位置
if(maps[i][j]=='@'){
startx = i;
starty = j;
}
}
}
dfs(startx,starty);
cout<<res<<endl;
return 0;
}
20210206添加
再来补充一题,这题在基础dfs上需要改进一下
题目描述:
Description
下面的矩阵可以想象成鸟瞰一座山,矩阵内的数据可以想象成山的高度。
可以从任意一点开始下山。每一步的都可以朝“上下左右”4个方向行走,前提是下一步所在的点比当前所在点的数值小。
例如处在18这个点上,可以向上、向左移动,而不能向右、向下移动。
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
问题是,对于这种的矩阵,请计算出最长的下山路径。
对于上面所给出的矩阵,最长路径为25-24-23-22-21-20-19-18-17-16-15-14-13-12-11-10-9-8-7-6-5-4-3-2-1,应输出结果25。
Input
输入包括多组测试用例。
对于每个用例,第一行包含两个正整数R和C分别代表矩阵的行数和列数。(1 <= R,C <= 100)
从第二行开始是一个R行C列矩阵,每点的数值在[0,10000]内。
Output
输出最长的下山路径的长度。
Sample Input
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
Sample Output
25
Hint
深度优先搜索
思路:
我们要找出最长的路,我们尝试从每个点出发,找到最大的。接着尝试每个方向(当然这个方向也得符和条件,dfs返回值就是路径长度),使用备忘录来剪枝!
最最重要的就是!也是多组输入最易错又很难察觉到的。我们要重置记录型数据!
题解
#include <iostream>
using namespace std;
int row,col;
int maps[105][105];
int memo[105][105];
int MaxNum(int i,int j){
return i>j ? i:j;
}
int dfs(int i,int j){
// 边界
if(i<0||j<0||i>=row||j>=col) return 0;
// 如果存在备忘录则直接返回
if(memo[i][j]) return memo[i][j];
int maxValue = 0;
// 尝试符合条件的每个方向
if(maps[i][j]>maps[i+1][j])
maxValue = MaxNum(dfs(i+1,j),maxValue);
if(maps[i][j]>maps[i-1][j])
maxValue = MaxNum(dfs(i-1,j),maxValue);
if(maps[i][j]>maps[i][j-1])
maxValue = MaxNum(dfs(i,j-1),maxValue);
if(maps[i][j]>maps[i][j+1])
maxValue = MaxNum(dfs(i,j+1),maxValue);
// 记录
memo[i][j] = maxValue + 1;
return memo[i][j];
}
// 多组测试时记住清空记录型数据
int main(){
while(cin>>row>>col){
for(int i=0;i<row;i++){
for(int j=0;j<col;j++){
cin>>maps[i][j];
// 重置备忘录
memo[i][j] = 0;
}
}
int res = 0;
for(int i=0;i<row;i++){
for(int j=0;j<col;j++){
res = MaxNum(res,dfs(i,j));
}
}
cout<<res<<endl;
}
return 0;
}