⭐纵星河万里,亦不及你一垂眸。 --Samsara_soul
🙌题单(List-DFS 01)
由浅入深 循序渐进
📋A 洛谷-P2089 烤鸡
Label 指数型枚举 模板本板
Level 普及-
📋B 洛谷-P1088 [NOIP2004 普及组] 火星人
Label 排列型枚举 可行性剪枝
Level 普及-
📋C 洛谷-P1149 [NOIP2008 提高组] 火柴棒等式
Label 指数型枚举 可行性剪枝
Level 普及-
📋D 洛谷-P1219 [USACO1.5] 八皇后 Checker Challenge
Label 暴力搜索 模板
Level 普及/提高-
题型分类 参考博客 DFS(深度优先搜索)8种题型
剪枝策略 参考博客 深搜的剪枝技巧C++详解
---A 题 详 解---
🟢原题回顾🟢
📋A 洛谷-P2089 烤鸡
Label 指数型枚举
Level 普及-
题目背景
猪猪 Hanke 得到了一只鸡。
题目描述
猪猪 Hanke 特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke 吃鸡很特别,为什么特别呢?因为他有 种配料(芥末、孜然等),每种配料可以放 到 克,任意烤鸡的美味程度为所有配料质量之和。 ==加入每种配料质量有三种选择,共加入十种配料,可能方案数== 现在, Hanke 想要知道,如果给你一个美味程度 ,请输出这 种配料的所有搭配方案。
输入格式
一个正整数 ,表示美味程度。
输出格式
第一行,方案总数。
==题目要求先输出方案总数res,而方案总数需要在搜索结束时才可得到。因此,需要使用数组mem[][10]存储可行方案==
第二行至结束, 个数,表示每种配料所放的质量,按字典序排列。 ==每种配料方案数为3,故而是指数型枚举== ==什么是字典序 ⭐参考 百度百科-字典序==
如果没有符合要求的方法,就只要在第一行输出一个 。
样例 #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
提示
对于 的数据,。
原题回顾 ==END==
🟢AC代码及注解🟢
#include<iostream>
using namespace std;
const int N = 12; //略开大些亦可
int n; // 食材美味度 输入数据
int arr[N]; //存放当前方案
int res; //记录方案数
int mem[10000][N]; // 用于记录答案
void dfs(int x,int sum) //x表示搜索到的层数 sum表示当前食物美味度
{
if(sum > n) return ; //可行性剪枝
if(x>10){ //遍历到第十项
if(sum == n){ //满足要求的解
res++; //计数器++
for(int i=1;i<=10;i++){
mem[res][i]=arr[i]; //存放可行解
}
}
return ; //注意return位置 不在if语句内
}
for(int i=1;i<=3;i++) //遍历过程
{
arr[x] = i; //记录步骤到当前方案
dfs(x+1,sum+i);
arr[x] = 0; //重置步骤
}
}
int main()
{
cin >> n;
dfs(1,0); //从第一项起搜索
//以下为打印答案
cout << res << "\n";
for(int i=1;i<=res;i++)
{
for(int j=1;j<=10;j++)
{
cout << mem[i][j] <<" ";
}
cout << "\n";
}
return 0;
}
🟢洛谷评测🟢
---B 题 详 解---
🟢原题回顾🟢
📋B 洛谷-P1088 [NOIP2004 普及组] 火星人
Label 排列型枚举 可行性剪枝
Level 普及-
题目描述
人类终于登上了火星的土地并且见到了神秘的火星人。人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这个数字的含义后,再把一个很小的数字加到这个大数上面,把结果告诉火星人,作为人类的回答。 ==步骤 ——1.破解含义 2.加上一个较小数 3.翻译结果== 火星人用一种非常简单的方式来表示数字――掰手指。火星人只有一只手,但这只手上有成千上万的手指,这些手指排成一列,分别编号为 。火星人的任意两根手指都能随意交换位置,他们就是通过这方法计数的。
一个火星人用一个人类的手演示了如何用手指计数。如果把五根手指――拇指、食指、中指、无名指和小指分别编号为 和 ,当它们按正常顺序排列时,形成了 位数 ,当你交换无名指和小指的位置时,会形成 位数 ,当你把五个手指的顺序完全颠倒时,会形成 ,在所有能够形成的 个 位数中, 最小,它表示 ; 第二小,它表示 ; 最大,它表示 。下表展示了只有 根手指时能够形成的 个 位数和它们代表的数字:
| 三进制数 | 代表的数字 |
|---|---|
==显而易见的是 火星人的每根手指都是唯一的,不可能在两个位置同时出现。手指序列即1~n的全排列【排列型枚举】== 现在你有幸成为了第一个和火星人交流的地球人。一个火星人会让你看他的手指,科学家会告诉你要加上去的很小的数。你的任务是,把火星人用手指表示的数与科学家告诉你的数相加,并根据相加的结果改变火星人手指的排列顺序。输入数据保证这个结果不会超出火星人手指能表示的范围。
输入格式
共三行。
第一行一个正整数 ,表示火星人手指的数目()。
第二行是一个正整数 ,表示要加上去的小整数()。
下一行是 到 这 个整数的一个排列,用空格隔开,表示火星人手指的排列顺序。
输出格式
个整数,表示改变后的火星人手指的排列顺序。每两个相邻的数中间用一个空格分开,不能有多余的空格。
样例 #1
样例输入 #1
5
3
1 2 3 4 5
样例输出 #1
1 2 4 5 3
提示
对于 的数据,。
对于 的数据,。
对于 的数据,。
原题回顾 ==END==
🟢详解🟢
不剪枝代码(WA示例)
#include <iostream>
using namespace std;
const int N = 10010; //外星人手指数不超过10000
int n; // 外星人手指数 输入数据
int m; // 要加上的小数 输入数据
int mars[N]; // 记录外星人初始排列 输入数据
int arr[N]; // 记录当前外星人手指序
bool st[N]; // 记录是否被选(手指是唯一的 一根手指不能同时出现在两个地方)
int res; //记录递增次数
//bool return0 = false; //停止标志
void dfs(int x)
{
//if (return0)
// return;
if (x > n){ //遍历到尾部
res++;
if (res == m + 1){ //目标答案在此
//return0 = true;
for (int i = 1; i <= n; i++){
cout << arr[i] << " "; //输出答案
}
}
return;
}
for (int i = 1; i <= n; i++){
if (!res){ //最重要的一步 需要基于外星人所给数字开始遍历
i = mars[x];
}
if (!st[i]){
arr[x] = i;
st[i] = true;
dfs(x + 1);
arr[x] = 0;
st[i] = false;
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++){
scanf("%d", &mars[i]);
}
dfs(1);
return 0;
}
评测结果
过程分析
此代码运行过程中到达指定解处可以输出正确答案,但是搜索继续向后进行,时间开销过大。
此时不难看出题目中较小数之深意
优化
增加bool return0 = false; //停止标志
dfs函数修改
void dfs(int x)
{
if (return0)
return;
if (x > n){ //遍历到尾部
res++;
if (res == m + 1){ //目标答案在此
return0 = true;
for (int i = 1; i <= n; i++){
cout << arr[i] << " "; //输出答案
}
}
return;
}
for (int i = 1; i <= n; i++){
if (!res){ //最重要的一步 需要基于外星人所给数字开始遍历
i = mars[x];
}
if (!st[i]){
arr[x] = i;
st[i] = true;
dfs(x + 1);
arr[x] = 0;
st[i] = false;
}
}
}
至此 dfs搜到正确答案后即可华丽退出🙌
洛谷评测
---C 题 详 解---
🟢原题回顾🟢
📋C 洛谷-P1149 [NOIP2008 提高组] 火柴棒等式
Label 排列型枚举 可行性剪枝
Level 普及-
题目描述
给你 根火柴棍,你可以拼出多少个形如 的等式?等式中的 、、 是用火柴棍拼出的整数(若该数非零,则最高位不能是 )。用火柴棍拼数字 的拼法如图所示:
注意:
- 加号与等号各自需要两根火柴棍;
- 如果 ,则 与 视为不同的等式();
- 根火柴棍必须全部用上。
==所有等式都含有
+ 与 -其共耗费四根火柴棍,此项火柴开销固定==
输入格式
一个整数 。
输出格式
一个整数,能拼成的不同等式的数目。
样例 #1
样例输入 #1
14
样例输出 #1
2
样例 #2
样例输入 #2
18
样例输出 #2
9
提示
【输入输出样例 1 解释】
个等式为 和 。
【输入输出样例 2 解释】
个等式为
、、、、、、、、。
原题回顾 ==END==
🟢详解🟢
不够完美的AC代码
#include<iostream>
using namespace std;
const int N = 1000;
int n;
int fire[10]={6,2,5,5,4,5,6,3,7,6}; //一一对应的火柴数
int arr[4]; //存储等式中的三个数字A B C
int res; //方案数
int cal(int num) //数组中没有的,使用已有数据计算
{
int add_sum=0;
if(num<10) return fire[num];
else
{
while(num){
add_sum+=fire[(num%10)];
num/=10;
}
return add_sum;
}
}
void dfs(int x,int sum) //A+B=C中 x=1,2,3 分别对应A,B,C sum是使用火柴数
{
//剪枝
if(sum>n) return ;
if(x>3){
if(sum==n && arr[1]+arr[2]==arr[3]){ //同时满足题中两个条件1、给定火柴数 2、等式成立
res++;
}
return ;
}
for(int i=0;i<=N;i++){ //普普通通搜一搜
arr[x]=i;
dfs(x+1,sum+cal(i)); //此处调用cal函数
arr[x]=0;
}
}
int main(){
cin >> n;
n=n-4;
dfs(1,0);
printf("%d",res);
return 0;
}
洛谷评测
优化
注意到10以上的fire可能会大量的重复用到,故可把cal递归方式改为递推,并且使用数组存储
#include<iostream>
using namespace std;
const int N = 1000;
int n;
int fire[N]={6,2,5,5,4,5,6,3,7,6}; //一一对应的火柴数
int arr[4]; //存储等式中的三个数字A B C
int res; //方案数
void dfs(int x,int sum) //A+B=C中 x=1,2,3 分别对应A,B,C sum是使用火柴数
{
//剪枝
if(sum>n) return ;
if(x>3){
if(sum==n && arr[1]+arr[2]==arr[3]){ //同时满足题中两个条件1、给定火柴数 2、等式成立
res++;
}
return ;
}
for(int i=0;i<N;i++){ //普普通通搜一搜
arr[x]=i;
dfs(x+1,sum+fire[i]); //此处直接使用数组数据
arr[x]=0;
}
}
int main(){
cin >> n;
n=n-4;
for(int i=10;i<N;i++){
fire[i]=fire[i/10]+fire[i%10];
}
dfs(1,0);
printf("%d",res);
return 0;
}
洛谷评测
对于测试样例 优化后代码效率提高了一倍左右
若还想优化,可以试着卡的范围,不过有一定的风险,可能会漏可行解。(实际上N可以卡到600)
此题完结😊
---D 题 详 解---
🟢原题回顾🟢
📋D 洛谷-P1219 [USACO1.5] 八皇后 Checker Challenge
Label 暴力搜索
Level 普及/提高-
[USACO1.5] 八皇后 Checker Challenge
题目描述
一个如下的 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列 来描述,第 个数字表示在第 行的相应位置有一个棋子,如下:
行号
列号
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 个解。最后一行是解的总个数。
输入格式
一行一个正整数 ,表示棋盘是 大小的。
输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
样例 #1
样例输入 #1
6
样例输出 #1
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
提示
【数据范围】
对于 的数据,。
原题回顾 ==END==
🟢AC代码🟢
还是比较简单的,就直接放代码了😊
#include<iostream>
using namespace std;
const int N = 15;
int n; //矩阵规模
int arr[N]; //记录摆放位置
bool st[N],lst[2*N-1],rst[2*N-1]; //列 左与右
int res;
void dfs(int x)
{
if(x>n){
res++;
if(res<4){
for(int i=1;i<=n;i++){
cout << arr[i] << ' ';
}
cout << '\n';
}
return ;
}
for(int i=1;i<=n;i++){
if(!st[i]&&!lst[i+x]&&!rst[i-x]){
st[i]=true;
lst[i+x]=true;
rst[i-x]=true;
arr[x]=i;
dfs(x+1);
st[i]=false;
lst[i+x]=false;
rst[i-x]=false;
arr[x]=0;
}
}
}
int main()
{
cin >> n;
dfs(1);
cout << res;
return 0;
}
或许还能优化下🤞
小声哔哔
刚接触八皇后问题的同学可以参考下知名vup@木子喵neko 的视频教程喵~ 哔哩哔哩 -【neko算法课】DFS与BFS【5期】
🟪感谢阅读喵 关注作者谢谢喵~💕