枚举-dfs深度优先搜索
枚举-深度优先搜索
文章目录
- 枚举-深度优先搜索
- 前备知识
- dfs题目一 全排列问题
- 题目描述
- 输入格式
- 输出格式
- 输入输出样例 #1
- 输入 #1
- 输出 #1
- 说明/提示
- 代码
- dfs题目二 烤鸡
- 题目背景
- 题目描述
- 输入格式
- 输出格式
- 输入输出样例 #1
- 输入 #1
- 输出 #1
- 说明/提示
- 代码
- 枚举-题目1: 完全平方数分解(简单)
- 题目描述:
- 输入:
- 输出:
- 代码
- 枚举-题目2:分苹果问题
- 题目描述:
- 输入:
- 输出:
- 代码
- 枚举-题目3:找纸币问题
- 题目描述:
- 输入格式:
- 输出格式:
- 代码
- 总结
前备知识
dfs和枚举的关系有点玄妙。
先说枚举 ,枚举就是把所有可能的结果全都列出来,怎么才能找到所有结果,有的很简单,比如找质数,一个函数和一个循环就可以。但是有的就不那么简单,比如写出1,2,3三个数的全排列,就要些手法。
dfs也就是深度优先搜索。什么是深度优先?可以理解成一条路走到黑(走得够深),如果路被堵住了(或者找到目标),就退回到上一个路口看看有没有其他的路(回溯)。这就有点试错的意味,利用计算机快速的计算能力,穷举出答案(枚举)。
dfs有的时候就像是填字游戏。比如给你四个空,1,2,3,4填上去,有几种填法?432*1种填法,列出来就是24种(全排列)对不对?可是如果是5个数字呢(120种)6个数字呢(720种)?这个时候当然不能手工写出来,要让计算机自己打出来,如何写一个程序就是关键了。
我们要处理什么?
- 如何说明完成现在这一步,递归的形式完成下一步?(比如我第一个数字填了1,如何表示后面的数字组合形式(比如第二个数字填2或3))
- 如何剪枝优化,提升效率(剪枝,就是减少那些不必要的步骤,比如通常来说我回家要经过四个路口,我尝试不同路线回家的时候,有些路一眼就知道走不通,就直接放弃(也就是不是都要走够四个路口))
dfs题目一 全排列问题
题目描述
按照字典序输出自然数 111 到 nnn 所有不重复的排列,即 nnn 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。
输入格式
一个整数 nnn。
输出格式
由 1∼n1 \sim n1∼n 组成的所有不重复的数字序列,每行一个序列。
每个数字保留 555 个场宽。
输入输出样例 #1
输入 #1
3
输出 #1
1 2 31 3 22 1 32 3 13 1 23 2 1
说明/提示
1≤n≤91 \leq n \leq 91≤n≤9。
代码
#include<iostream>
#include<iomanip>
using namespace std;
int arr[25] = { 0 };
bool used[21] = { 0 };
int n;
void print() {for (int i = 1; i < n; i++){cout << setw(5) << arr[i];}cout << setw(5) << arr[n] << endl;
}
void dfs(int x){//x是当前是第x格,从0开始if (x == n){print();return;}//dfs 相当于填拼图,一个位置放好之后看下一个位置,填满之后又回退到上一个步骤,看看有什么其他可能for (int i = 1; i <= n; i++) {if (used[i])continue;//如果该数字被使用过,则跳过arr[x+1] = i;//第x个位置放入拼图used[i] = true;//标记避免重复使用dfs(x + 1);//填下一个位置used[i] = false;//回退}//拿1的情况来说,刚开始填了1,1被标记为用过了,x+1格的位置填上其他的//回退到1的时候,又把1标记成未使用的,开始2开头的深度搜索return;
}
int main(){cin >> n;dfs(0);//从0位置开始,从1开始会提前结束(少了一次深入)return 0;
}
dfs题目二 烤鸡
题目背景
猪猪 Hanke 得到了一只鸡。
题目描述
猪猪 Hanke 特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke 吃鸡很特别,为什么特别呢?因为他有 101010 种配料(芥末、孜然等),每种配料可以放 111 到 333 克,任意烤鸡的美味程度为所有配料质量之和。
现在, Hanke 想要知道,如果给你一个美味程度 nnn ,请输出这 101010 种配料的所有搭配方案。
输入格式
一个正整数 nnn,表示美味程度。
输出格式
第一行,方案总数。
第二行至结束,101010 个数,表示每种配料所放的质量,按字典序排列。
如果没有符合要求的方法,就只要在第一行输出一个 000。
输入输出样例 #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≤5000n \leq 5000n≤5000。
代码
#include<iostream>
#include<vector>
using namespace std;
vector<vector<int>>solutions;//记录方案
vector<int>current(10);//记录十种结果
void dfs(int pos, int remaining)//pos表示处理的位置,remaining表示处理剩余部分
{if (pos == 10) {if (remaining == 0)solutions.push_back(current);return;//处理到最后一个也还有剩余,舍弃掉}int min_remainning = remaining - (10 - pos) * 3;//剩下的是否可以凑出来int max_remainning = remaining - (10 - pos)*1;// 剪枝//减少递归的次数,比如我来个35,直接就爆了,递归也不递归了if (min_remainning > 0 || max_remainning < 0)return;//递归for (int i = 1; i <= 3; i++) {if (remaining >= i) {current[pos] = i;dfs(pos + 1, remaining - i);}}
}
int main() {int n;cin >> n;if (n < 10 || n > 30) {cout << 0 << endl;return 0;}dfs(0, n);//current的下标从0开始//solutions可以按字典序存的原因是,一直递推到最后,自然是pos==10满足的放进去//而且是dfs()中pos放入的小的先递归结束cout << solutions.size() << endl;for (const auto& solution : solutions){for (int i = 0; i < 10; i++)cout << solution[i]<<' ';cout << endl;}return 0;
}
枚举-题目1: 完全平方数分解(简单)
题目描述:
给定一个正整数n,判断能否将n表示为两个完全平方数的和。
输入:
一个正整数n (1 ≤ n ≤ 10000)
输出:
如果能表示为两个完全平方数的和,输出"YES",否则输出"NO"
代码
#include <iostream>
#include <cmath>
using namespace std;int main() {int n;cin >> n;for (int i = 0; i * i <= n; i++) {int remaining = n - i * i;int j = sqrt(remaining);if (j * j == remaining) {cout << "YES" << endl;return 0;}}cout << "NO" << endl;return 0;
}
枚举-题目2:分苹果问题
题目描述:
有n个苹果要分给k个小朋友,每个小朋友至少分到1个苹果。问有多少种不同的分配方案?
输入:
两个正整数n和k (k ≤ n ≤ 20)
输出:
分配方案的数量
代码
#include <iostream>
using namespace std;int count_ways = 0;void enumerate(int remaining, int people, int min_val) {if (people == 1) {if (remaining >= min_val) {count_ways++;}return;}// 枚举当前人可以分到的苹果数for (int give = min_val; give <= remaining - people + 1; give++) {enumerate(remaining - give, people - 1, give);}
}int main() {int n, k;cin >> n >> k;count_ways = 0;enumerate(n, k, 1);cout << count_ways << endl;return 0;
}
枚举-题目3:找纸币问题
题目描述:
给定金额n美元,用面值为100, 20, 10, 5, 1的纸币支付,求最少需要多少张纸币。
输入格式:
一个整数n (1≤n≤10^9)
输出格式:
最少纸币数
代码
#include <iostream>
using namespace std;int main() {int n;cin >> n;int bills[] = {100, 20, 10, 5, 1};int count = 0;//从最大的纸币开始,不够一整张就看小的面额的纸币for (int i = 0; i < 5; i++) {count += n / bills[i];n %= bills[i];}cout << count << endl;return 0;
}
总结
DFS 的浪漫主义:在看似无望的深渊中,坚信必有出路;在每一次的回溯中,都带着前路的智慧重新出发。说白了,dfs就是一个试错的过程,不撞南墙不回头,人生没有回溯,每一个岔路口的选择都是未知的,但总要走下去。
ps:其他题目下一篇再分享啦,下期见