浅说深度优先搜索(中)——回溯

写在最前

相信在你们不懈的努力之下,基本的递归一定可以写出来了,那么我们现在就来看看递归的升级版——回溯怎么写吧!

简说回溯

递归是一种特别重要的解题策略。大部分题目需要找到最优解,而这个求解过程可能存在一定的规律性,比如贪心,递推等,但是大部分情况可能只能暴力枚举所有情况找到答案,而暴力枚举这个过程简单的可以用循环实现,但是对于一些比较复杂的情况,需要用递归来实现。
比如数独游戏,在最开始的时候,每个空格子有很多种可能,我们需要先尝试填入一个数,在此基础上再去尝试填入其他格子,在填数过程中,我们会发现当前这种方案可能是错误的,那么我们就需要返回上一步,重新尝试其他可能,这种解题办法我们称为回溯法。

还是老样子,直接用例题来提高熟练度

全排列问题

全排列问题

题目描述

按照字典序输出自然数 1 1 1 n n n 所有不重复的排列,即 n n n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式

一个整数 n n n

输出格式

1 ∼ n 1 \sim n 1n 组成的所有不重复的数字序列,每行一个序列。

每个数字保留 5 5 5 个场宽。

样例 #1

样例输入 #1

3

样例输出 #1

    1    2    31    3    22    1    32    3    13    1    23    2    1

提示

1 ≤ n ≤ 9 1 \leq n \leq 9 1n9

全排列问题是数学上的一类经典问题,在数学上存在排列公式可以直接快速求解。
但是在实际问题中可能存在一些其他的限制条件,比如某个位置不能放某个数,某个数必须放在某个位置,这种情况下用公式就比较麻烦,所以先考虑一种比较效率低下但是比较万能的方法。
简单来说就是对于每一个位置,依次放入一个之前未使用过的数,当把所有数都放入之后,就表示存在一种答案,当把每个位置的所有情况都尝试之后,累加得到的答案就是正确答案。
我们可以把该题看做有n个格子,依次要把n个数放入这些格子中。对于每一个格子而言,放数的方法都是相同的——找到一个之前未放入的数。
那么我们如何才能知道这个数是否已经使用过了呢?比较简单的办法就是开一个book数组,标记1—n中每一个数的状态。
每次放数的时候,需要考虑两个问题。
第一:所有情况都不能遗漏,即从1枚举到n。
第二:之前放过的数不能重复放,即未被标记。
当n个格子中都有数的时候表示放数结束,得到一种正确方案,记录答案并返回。
前面的做法只考虑了一种情况,比如n=3的时候,找到的第一种排列为1 2 3,接下来我们应该先考虑第3个格子是否还可以放其他数,发现不可以放其他数,那么应该继续返回上一次,即回到第二个格子,第二个格子之前放了数字2,我们考虑是否可以放下一个数字3,发现可以,当第二个格子放了数字3之后,继续考虑第三个格子,发现可以放数字2,此时又找到一种可能的情况。接着继续返回尝试其他可能,当返回到第一个格子的时候,发现可以放数字2,继续到第二个格子,可以放数字1,到第三个格子,可以放数字3,又是一种情况…一次尝试和返回,直到返回主函数为止。

ACcode

#include <bits/stdc++.h>
using namespace std;int tmp[100];
bool used[100];
void dfs(int n,int step){if (step==n){for (int i=0;i<n;i++){printf("%5d",tmp[i]);}cout<<endl;return;}for (int i=1;i<=n;i++){if (used[i]==0){tmp[step]=i;used[i]=1;dfs(n,step+1);used[i]=0;//回溯}}
}int main() {int n;cin>>n;dfs(n,0);return 0;
}

借书问题

在这里插入图片描述
对于输入的每个同学喜欢的书的情况,可以开一个二维数组来表示,a[i][j]表示第i位同学是否喜欢第j本书。
对于每一个同学是否可以借这一本书,考虑的情况都是相同的:
(1)这本书是否存在
(2)第i个同学是否喜欢这本书
(3)这本书是否已经在前面被借走了。
当这n位同学都可以借到一本书,那么就表示这种方案是合理的,如果某位同学无书可以借,那么就说明这种方案是错误的,就需要返回上一位同学重新借其他书。
该题还需要输出借书的方案,即我们需要记录每位同学借了哪一本书,再开一个b数组,b[i]=j表示第i位同学借了第j本书。
由于方案不止一种,输出的方案需要按照字典序排列。即前面的同学先借编号小的书,那么我们在枚举的时候就只能从1开始枚举到n,这样得到的可行方案就是按照字典序从小到大排列的。
还有一些题目要求按照字典序从大到小排列,即前面的同学先借编号大的书,这种情况我们需要从n到1枚举。

ACcode

#include<bits/stdc++.h>
using namespace std;int n,a[15][15],cnt;
int ans[1000][15],b[15],used[20];inline int read(){int x=0;char ch=0;while (ch>'9'||ch<'0')ch=getchar();while (ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();return x;
}void dfs(int k){if (k==n){for (int i=0;i<n;i++){ans[cnt][i]=b[i];}cnt++;}else {for (int i=0;i<n;i++){if (used[i]==0&&a[k][i]==1){used[i]=1;b[k]=i+1;dfs(k+1);used[i]=0;}}}
}
int main(){n=read();for (int i=0;i<n;i++){for (int j=0;j<n;j++){cin>>a[i][j];}}dfs(0);cout<<cnt<<endl;for (int i=0;i<cnt;i++){for (int j=0;j<n;j++){cout<<ans[i][j]<<" ";}cout<<endl;}return 0;
}

好了,基础的部分已经搞完了,现在我们来看看组合问题
前面我们遇到的问题都是用回溯法求解排列类问题,即选择相同的元素,但是顺序不同算不同的方案。很多时候还会出现另一类问题——组合数问题。即从n个元素中选择m个元素的一个组合,组合和排列的区别在于,组合和顺序没有关系只要每个元素都相同,顺序不同也只算作一种方案,即1,1,5和5,1,1是一个相同的组合。

组合的输出

题目描述

排列与组合是常用的数学方法,其中组合就是从 n n n 个元素中抽出 r r r 个元素(不分顺序且 r ≤ n r \le n rn),我们可以简单地将 n n n 个元素理解为自然数 1 , 2 , … , n 1,2,\dots,n 1,2,,n,从中任取 r r r 个数。

现要求你输出所有组合。

例如 n = 5 , r = 3 n=5,r=3 n=5,r=3,所有组合为:

123 , 124 , 125 , 134 , 135 , 145 , 234 , 235 , 245 , 345 123,124,125,134,135,145,234,235,245,345 123,124,125,134,135,145,234,235,245,345

输入格式

一行两个自然数 n , r ( 1 < n < 21 , 0 ≤ r ≤ n ) n,r(1<n<21,0 \le r \le n) n,r(1<n<21,0rn)

输出格式

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。

注意哦!输出时,每个数字需要 3 3 3 个场宽。以 C++ 为例,你可以使用下列代码:

cout << setw(3) << x;

输出占 3 3 3 个场宽的数 x x x。注意你需要头文件 iomanip

样例 #1

样例输入 #1

5 3

样例输出 #1

1  2  31  2  41  2  51  3  41  3  51  4  52  3  42  3  52  4  53  4  5

该题的解题策略和前面类似,都是用回溯法尝试所有可能性,找出符合条件的所有方案,但是该题也有和前面不一样的地方,如果按照前面的方式枚举,那么最终得到的答案肯定是有重复的,而且重复了很多次,那么有没有办法避免这种重复的情况呢?要怎么样枚举才能避免重复情况呢?
根据题目的输出我们可以发现,下一个数一定是大于前一个数,这就是一种很好的避免重复的办法,只要我们保证了下一个数大于前一个数,这样一个组合的满足要求的排列有且仅有一个,而且刚好满足字典序最小的要求。

ACcode

#include <bits/stdc++.h>
using namespace std;
int r, n;
int temp[21];void print () {for (int i = 0; i < r; i++) {printf("%3d", temp[i]);}cout << endl;
}void f(int start, int k ) { //k 还有多少个数字没有选if (k == 0) {print();//打印结果return;}for (int i = start; i <= n ; i++) {temp[r - k] = i;f(i + 1, k - 1);}
}int main() {cin >> n >> r;f(1, r);return 0;
}

放苹果

题目描述

m m m 个同样的苹果放在 n n n 个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法。( 5 , 1 , 1 5,1,1 5,1,1 1 , 1 , 5 1,1,5 1,1,5 是同一种方法)

输入格式

第一行是测试数据的数目 t t t,以下每行均包括二个整数 m m m n n n,以空格分开。

输出格式

对输入的每组数据 m m m n n n,用一行输出相应的结果。

样例 #1

样例输入 #1

1
7 3

样例输出 #1

8

样例 #2

样例输入 #2

3
3 2
4 3
2 7

样例输出 #2

2
4
2

提示

对于所有数据,保证: 1 ≤ m , n ≤ 10 1\leq m,n\leq 10 1m,n10 0 ≤ t ≤ 20 0 \leq t \leq 20 0t20

根据题目中的要求,该题仍然是一道组合数问题,即跟选中元素的顺序无关,所以枚举的时候保证下一个元素大于等于下一个元素即可,注意这里可以等于。
但是该题的结束条件有点不一样,当n个盘子都放了苹果之后是不是就一定是一种正确方案了呢?并不是,因为还要保证苹果必须要放完,所以我们还需要记录剩余的苹果数量(或者已经放了的苹果数量)。
同样,当前盘子可以放的苹果数量的范围为k到剩余苹果的数量,甚至还可以精确到剩余苹果数量除以剩余盘子数量。

#include <bits/stdc++.h>
using namespace std;
int t;int sol(int m, int n) { //m为苹果,n为盘子if (n == 1 || m == 0)return 1;if (m < n)return sol(m, m);if (m >= n)return sol(m, n - 1) + sol(m - n, n);
}int  main() {cin >> t;int m, n;for (int i = 1; i <= t; i++) {cin >> m >> n;cout << sol(m, n) << endl;}return 0;
}

烤鸡

题目背景

猪猪 Hanke 得到了一只鸡。

题目描述

猪猪 Hanke 特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke 吃鸡很特别,为什么特别呢?因为他有 10 10 10 种配料(芥末、孜然等),每种配料可以放 1 1 1 3 3 3 克,任意烤鸡的美味程度为所有配料质量之和。

现在, Hanke 想要知道,如果给你一个美味程度 n n n ,请输出这 10 10 10 种配料的所有搭配方案。

输入格式

一个正整数 n n n,表示美味程度。

输出格式

第一行,方案总数。

第二行至结束, 10 10 10 个数,表示每种配料所放的质量,按字典序排列。

如果没有符合要求的方法,就只要在第一行输出一个 0 0 0

样例 #1

样例输入 #1

11

样例输出 #1

10
1 1 1 1 1 1 1 1 1 2 
1 1 1 1 1 1 1 1 2 1 
1 1 1 1 1 1 1 2 1 1 
1 1 1 1 1 1 2 1 1 1 
1 1 1 1 1 2 1 1 1 1 
1 1 1 1 2 1 1 1 1 1 
1 1 1 2 1 1 1 1 1 1 
1 1 2 1 1 1 1 1 1 1 
1 2 1 1 1 1 1 1 1 1 
2 1 1 1 1 1 1 1 1 1

提示

对于 100 % 100\% 100% 的数据, n ≤ 5000 n \leq 5000 n5000

该题的解题思路比较简单,对于每一种调料,考虑[1,4]两种情况,主要在于时间复杂度的优化。
因为每种调料的范围是[1,4],在考虑当前第i种调料的时候,之前调料和为sum,需要满足第一个条件是sum+i+(10-step)<=n,即后面每种调料至少要放1g,一共是(10-step)g,还需要满足第二个条件是sum+i+4*(10-step)>=n,即后面每种调料最多放4g。
对于回溯类题目,由于比赛中爆搜一般都不是正解,所以我们要尽可能优化搜索的效率,这样才能得到更高的分数,需要考虑前面是否合理以及后面是否合理。
但是这里作者偷了个懒,你看代码就知道了

#include <bits/stdc++.h>  
using namespace std;  int main()  {  int a,b,c,d,e,f,g,h,i,j,in,x=0;  cin>>in;  for (a=1;a<=4;a++)  {  for (b=1;b<=4;b++)  {  for (c=1;c<=4;c++)  {  for (d=1;d<=4;d++)  {  for (e=1;e<=4;e++)  {  for (f=1;f<=4;f++)  {  for (g=1;g<=4;g++)  {  for(h=1;h<=4;h++)  {  for (i=1;i<=4;i++)  {  for (j=1;j<=4;j++)  {  if (a+b+c+d+e+f+g+h+i+j==in){  x++;  }  }  }  }  }  }  }  }  }  }  }  cout<<x<<endl;  for (a=1;a<=4;a++){  for (b=1;b<=4;b++){  for (c=1;c<=4;c++){  for (d=1;d<=4;d++){  for (e=1;e<=4;e++)  {  for (f=1;f<=4;f++)  {  for (g=1;g<=4;g++)  {  for(h=1;h<=4;h++)  {  for (i=1;i<=4;i++)  {  for (j=1;j<=4;j++)  {  if (a+b+c+d+e+f+g+h+i+j==in){  cout<<a<<" ";  cout<<b<<" ";  cout<<c<<" ";  cout<<d<<" ";  cout<<e<<" ";  cout<<f<<" ";  cout<<g<<" ";  cout<<h<<" ";  cout<<i<<" ";  cout<<j<<endl;  }  }  }  }  }  }  }  }  }  }  } return 0; 
}

OK哈,这就是所有的回溯算法,再找个时间吧最后一部分写了就可以完结了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/1721.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UE5(基础动作)多人游戏制作蹲伏

1.创建输入操作&#xff0c;IA_Crouch 在输入映射中添加 IA_Crouch,在触发器中创建两个索引&#xff0c;已按下已松开来创建蹲伏输入。 蹲伏操作必须要勾选角色-角色移动-crouch勾选可蹲伏否则你的人物无法真正蹲下。 为蹲伏创建函数&#xff0c;创建布尔来判断是否蹲伏。 通过…

CodeGemma初探

什么是 CodeGemma CodeGemma是一系列强大而轻量级的模型的集合&#xff0c;可以执行各种编码任务&#xff0c;包括填充中间代码补全、代码生成、自然语言理解、数学推理和指令跟随。 版本&#xff1a; instruct&#xff1a;7B, 这个版本专门针对自然语言到代码聊天和指令跟随…

day83 AJAX

1什么是AJAX AJAX语法 AJAX Asynchronous JavaScript and XML 异步js和XML 实现页面某一部份更新&#xff0c;无需服务器转发或重定向 1 $.ajax() 语法: $.ajax( { "url" : "url&qu…

数据库主从复制

一、主从复制概述 1、介绍&#xff1a; 主从复制是指将主数据库的 DDL 和 DML 操作写入到二进制日志中&#xff0c;将二进制日志传送到从库服务器&#xff0c;然后在从库上对这些日志重新执行&#xff08;重做&#xff09;&#xff0c;从而使得从库和主库的数据保持同步。 M…

百面算法工程师 | 分类网络总结

欢迎大家订阅我的专栏一起学习共同进步&#xff0c;主要针对25届应届毕业生 祝大家早日拿到offer&#xff01; lets go http://t.csdnimg.cn/dfcH3 目录 4. 经典分类网络与发展 4.1 AlexNet 4.2 VGGNet 4.3 GoogLeNet Inception 4.4 ResNet 4.5 DenseNet 4.6 MobileN…

C#中的浅拷贝(Shallow Copy)和深拷贝(Deep Copy),深拷贝的集中实现方式,浅拷贝深拷贝的案例

C#中的浅拷贝&#xff08;Shallow Copy&#xff09;和深拷贝&#xff08;Deep Copy&#xff09; 拷贝就是创建一个对象&#xff0c;这个对象有着原始对象数据&#xff08;属性和字段&#xff09;的一份精确拷贝&#xff08;只针对Object和Array这样的引用数据类型&#xff09;…

截断堆积柱状图

本教程原文链接&#xff1a;截断堆积柱状图绘制教程 欢迎大家转载&#xff01;&#xff01;&#xff01;&#xff01; 本期教程 写在前面 堆积柱状图是柱状图的常见类型之一&#xff0c;也是平时使用概率较高的图形之一。我们前期发布了很多个柱状图的绘制教程&#xff0c;若你…

【工厂模式】简单工厂模式-简单例子

目录 一、简单介绍 二、未使用工厂模式之前 三、简单工厂模式 初始 优化 总结 一、简单介绍 工厂模式是一种软件设计模式&#xff0c;用于创建对象的方法。在工厂模式中&#xff0c;创建对象的逻辑被封装在一个单独的类中&#xff0c;该类负责根据特定条件或参数创建合适…

windows下使用命令uvicorn启动fastapi程序有乱码,方框形状奇怪字符

问题简述 执行命令uvicorn main:app --reload后出现的问题如图所示 这个问题非常容易解决&#xff01; 原因是windows控制台 默认未开启 ANSI颜色的支持 那么我们只需要开启就可以了 轻松解决 1 按下winR 打开运行 2 输入regedit 点击确定编辑注册表 3 新建值 值的名称为…

vue3组件封装系列-表单请求

我们在开发一些后台管理系统时&#xff0c;总是会写很多的列表查询页面&#xff0c;如果不封装组件&#xff0c;就会无限的复制粘贴&#xff0c;而且页面很冗余&#xff0c;正常情况下&#xff0c;我们都是要把组件进行二次封装&#xff0c;来达到我们想要效果。这里我分享一下…

《中学科技》是什么级别的刊物?如何投稿?

《中学科技》是什么级别的刊物&#xff1f;如何投稿&#xff1f; 《中学科技》创刊于1976年&#xff0c;由上海世纪出版&#xff08;集团&#xff09;有限公司主管&#xff0c;上海科技教育出版社有限公司主办的省级学术期刊&#xff0c;《中学科技》以传播科技知识、启迪智慧…

Centos安装软件失败There are no enabled repos.

这个错出现的原因可能是&#xff1a;没有先安装wget源&#xff0c;就把源给备份了 解决方案&#xff1a; 下载对应版本repo文件, 放入/etc/yum.repos.d/里&#xff0c;下载地址&#xff1a;CentOS镜像使用帮助 查看自己CentOS的版本&#xff0c;下载对应的repo文件&#xff…

spring boot中的标注@Component、@Service等

让我告诉你什么叫水货。 一、水货横行 一直以来&#xff0c;我对Spring Boot项目中的标注&#xff0c;像Component啦、Service啦、Configuration啦&#xff0c;甚至Autowired啦&#xff0c;等等&#xff0c;都似懂非懂。Autowired与Resource有什么区别也不清楚。 个中原因&a…

LearnOpenGL(三)之GLSL

一、GLSL 着色器是使用一种叫GLSL的类C语言写成的。 着色器的开头总是要声明版本&#xff0c;接着是输入和输出变量、uniform 和main函数。每个着色器的入口点都是main函数&#xff0c;在这个函数中 我们处理所有的输入变量&#xff0c;并将结果输出到输出变量中。 二、数据…

2-Embedding例子:简单NN网络、迁移学习例子(glove语料预训练)

一、简单例子&#xff1a;构造简单NN网络生成Embedding 1、pytorch例子 2、tensorflow例子 # 1导入模块 import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding import numpy as np# 2构建语料库 corpus[[…

Linux命令接着学习

which命令&#xff0c;找到各种命令程序所处在的位置 语法&#xff1a;which查找的命令 那么对于我们想查找其他类型文件所在的位置&#xff0c;我们可以用到find命令 find命令 选项为-name&#xff0c;表示按照文件名进行查找 find命令中通配符 find命令和前面rm命令一样&…

MT3023 歌词中找单词

1.暴力 10/12 #include <bits/stdc.h> using namespace std; int n; string a[10005]; int main() {cin >> n;for (int i 0; i < n; i)cin >> a[i];string ll;cin >> ll;for (int i 0; i < n; i){string u a[i];int num 0;int j 0;for (in…

解线性方程组——追赶法解三对角方程组 | 北太天元

一、问题描述 对于线性方程组 A x b , A ( b 1 c 1 a 2 b 2 c 2 ⋱ ⋱ ⋱ ⋱ ⋱ ⋱ a n − 1 b n − 1 c n − 1 a n b n ) , b ( f 1 f 2 ⋮ f n ) Axb,\quad A\begin{pmatrix}b_1&c_1&&&&\\a_2&b_2&c_2&&&\\&\ddots&\d…

CentOS 7安装、卸载MySQL数据库(一)

说明&#xff1a;本文介绍如何在CentOS 7操作系统下使用yum方式安装MySQL数据库&#xff0c;及卸载&#xff1b; 安装 Step1&#xff1a;卸载mariadb 敲下面的命令&#xff0c;查看系统mariadb软件包 rpm -qa|grep mariadb跳出mariadb软件包信息后&#xff0c;敲下面的命令…

mysql基础14——视图

视图 视图是一种虚拟表 可以把一段查询语句作为视图存储在数据库中 需要的时候把视图看作一个表&#xff0c;对里面的数据进行查询 视图并没有真正存储数据 避免了数据存储过程中可能产生的冗余 提高了存储的效率 子查询 嵌套在另一个查询中的查询 派生表 如果在查询中…