方法介绍
回溯是一种搜索方式,回溯的本质是穷举,穷举所有可能,然后选出一个想要的答案,如果想要回溯法更高效一点,可以加一些剪枝操作,但也改不了回溯法就是穷举的本质。 所以回溯法并不高效,使用他是因为没得选,有些问题能暴力出来就不错了,撑死再剪枝一下。 回溯一般能解决的问题如下:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
所有的回溯法都可以抽象成树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。
回溯伪代码:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
1336: 练57.1 全排列问题
#include <bits/stdc++.h>
using namespace std;
int f[11];//标记哪些数字使用过
int a[11];// 存放全排列的结果
int n;//输入的数字
//递归函数:为a数组每个元素赋值
void backtrack(int k){//k代表搜索的层数或深度
if(k>n){//所有层安置完(走到了最深),输出存储好的答案
for(int i=1;i<=n;i++){
printf("%5d",a[i]);
}
printf("\n");
}
for(int i=1;i<=n;i++){//当前层数对应的位置所有的安置可能
if(!f[i]){//如果前面的层还没用过i,就存起来并打标记
a[k]=i;
f[i]=true;//保留现场
backtrack(k+1);//继续向内层 递归
f[i]=false;//恢复
}
}
}
int main(){
scanf("%d",&n);
backtrack(1);
return 0;
}
2062: 大洪水
#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
char a[1005][1005];
void dfs(int x,int y){
if(x<1||x>n||y<1||y>m||a[x][y]=='0') return;
a[x][y]='0';
dfs(x+1,y);
dfs(x-1,y);
dfs(x,y+1);
dfs(x,y-1);
}
int main(){
cin>>n>>m;
memset(a,'0',sizeof(a));
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]!='0'){
ans++;
dfs(i,j);
}
}
}
cout<<ans;
}
P2802 回家
#include<bits/stdc++.h>
#define MAXN 10
#define inf 0x3f3f3f3f
using namespace std;
int a[MAXN][MAXN], vis[MAXN][MAXN];
int n, m, sx, sy, fx, fy;// (sx,sy)为起点坐标,(fx,fy)为终点坐标,当然n,m是行和列
int sum;//用来统计最少步数的,一开始要初始化为最大(如上面预处理inf)
int to[][2] = { {0,1},{0,-1},{-1,0},{1,0} };//模拟4个方向
void dfs(int u, int v,int ans,int t)//(u,v)代表当前小H所在坐标,ans记录当前的血量,t记录到目前为止已耗费的时间
{
if (u == fx && v == fy)//到家啦~
{
sum = min(sum, t);//保留最小步数
return;
}
if (t >= sum)//这个if是后加的,是为了剪枝(之前没加也过了。。)当然,如果都模拟到这一步了,小H还没到达终点时,t就已经比之前的一种到达家的路径所耗费时间更多了,这条路就不再下去了
return;
if (ans > 1)//这是之前所说的,我在模拟每一步之前,我的血量一定要大于1,不然就返回了(返回相当于这段的dfs没执行了,因为if外边下面啥没有= =)
{
for (int i = 0; i < 4; i++)//模拟出上下左右走
{
int q = u + to[i][0], w = v + to[i][1];//模拟走一格之后的坐标
if (q >= 0 && q < n&&w >= 0 && w < m && vis[q][w]<=22 && a[q][w] != 0)//走到这个格子时,我不能超边界&&限制每个格子所走的步数&&不能是大板墙~
{
vis[q][w] ++;//走过的次数+1
if (a[q][w] == 4)
{
dfs(q, w, 6, t + 1);//如果我走到了4了,当然我就回满血了~~ 并且步数+1
}
else
{
dfs(q, w, ans - 1, t + 1);//否则我走到这只是扣血,步数也要+1
}
vis[q][w] --;//减少这个格子的次数,消除影响
}
}
}
}
int main()
{
cin >> n >> m;
sum = inf;//初始化
memset(vis, 0, sizeof(vis));//初始化
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> a[i][j];
if (a[i][j] == 2)
sx = i, sy = j;//找出起点坐标
else if (a[i][j] == 3)
fx = i, fy = j;//找出家坐标
}
}
dfs(sx, sy, 6, 0);//从起点开始,血量为6,步数为0。开始深搜
if (sum == inf)//如果sum没有被更新,那就说明没有达到家
cout << "-1" << endl;
else
cout << sum << endl;//否则输出这个最短时间
return 0;
}