目录
1.DFS
1.1核心思想
DFS的核心思想可以用 “尝试所有可能,逐步构建解,不满足条件则回退” 来概括。它本质是一种有策略的穷举搜索,通过剪枝和状态回退机制高效地在解空间中寻找可行解。以下从三个维度深入解析其核心思想:
一、🌻解空间树与决策路径🌻
DFS将问题抽象为一棵解空间树,树的每个节点代表一个部分解(或决策状态):
- 根节点:初始状态(尚未做出任何选择)。
- 中间节点:已做出部分选择,尚未完成整个解。
- 叶节点:完整解或无效解。
核心操作:从根节点出发,通过递归向下扩展路径(做出选择),若发现当前路径不可能通向有效解,则回溯到父节点(撤销选择),尝试其他分支。
二、🌻剪枝函数:避免无效搜索🌻
关键在于剪枝策略,通过两类函数判断路径有效性:
- 约束函数:判断当前路径是否满足问题的约束条件(如组合问题中元素是否重复)。
- 限界函数:判断当前路径是否可能产生最优解(常用于优化问题,如旅行商问题)。
剪枝效果:若某节点被判定为无效,直接跳过其所有子树,大幅减少搜索空间。
示例:在全排列问题中,若当前路径已包含元素2,则后续选择跳过2,避免生成重复排列。三、🌻状态回退:恢复现场的艺术🌻
每次递归返回前,必须撤销当前选择,恢复到选择前的状态,确保后续分支不受影响。关键步骤:
- 选择:在当前节点做出一个选择,进入下一层递归。
- 递归:处理子问题。
- 撤销:递归返回后,撤销之前的选择,尝试其他可能性。
1.2适用场景
DFS用于解决需在复杂解空间中穷举所有可能组合、排列或路径,并通过约束条件剪枝筛选符合要求解的问题(如组合生成、棋盘布局、路径搜索等)。
1.3问题分类:定长&不定长📍📍📍
DFS 可大致分为两类问题:固定长度组合问题&不固定长度组合问题。
⚠️这两类问题的递归树有本质的不同,解体思路也有差异。
1.3.1🌻固定长度组合问题
- 目标:从 n 个元素中选出固定 k 个元素,生成所有不重复的组合。
- 递归树特征:
- 树的深度固定为 k(层数即已选元素数)。
- 每个节点的分支数逐渐减少(避免重复组合)。
- 关键参数:当前层数(控制递归深度)、起始下标(控制元素选择范围)。
- 剪枝条件:剩余元素不足时提前终止。
典型例题:全排列;飞机降落
递归树示例(从
[1,2,3]选 2 个数):dfs(0, []) ├── 选1 → dfs(1, [1]) │ ├── 选2 → dfs(2, [1,2]) ✅ │ └── 选3 → dfs(2, [1,3]) ✅ └── 选2 → dfs(1, [2]) └── 选3 → dfs(2, [2,3]) ✅
1.3.2🌻不固定长度组合问题
- 目标:从 n 个元素中选出任意数量元素,满足特定条件(如和为 k、元素个数最少等)。
- 递归树特征:
- 树的深度不固定,直到满足条件或无法继续。
- 每个节点有选 / 不选两个分支(或根据题意调整)。
- 关键参数:当前元素下标(控制选哪个元素)、当前状态(如和、元素个数)。
- 剪枝条件:根据目标条件动态剪枝(如和超过 k 时终止)。
典型例题:选数问题;最大团问题
递归树示例(从
[1,2,3]选和为 3 的组合):dfs(0, 0) ├── 选1 → dfs(1, 1) │ ├── 选2 → dfs(2, 3) → [1,2] ✅ │ └── 不选2 → dfs(2, 1) │ └── 选3 → dfs(3, 4) ❌ └── 不选1 → dfs(1, 0) ├── 选2 → dfs(2, 2) │ └── 选3 → dfs(3, 5) ❌ └── 不选2 → dfs(2, 0) └── 选3 → dfs(3, 3) → [3] ✅
1.3.3👍两类问题的代码模板对比
1. 固定长度组合模板
vector<vector<int>> result;
vector<int> path;
void dfs(int start, int depth, int k) {
if (depth == k) { // 层数达到k,收集结果
result.push_back(path);
return;
}
for (int i = start; i < n; i++) { // 从start开始选,避免重复
path.push_back(nums[i]);
dfs(i + 1, depth + 1, k); // 层数+1,继续递归
path.pop_back();
}
}
2. 不固定长度组合模板
vector<vector<int>> result;
vector<int> path;
void dfs(int idx, int current_sum, int target) {
if (current_sum == target) { // 满足条件,收集结果
result.push_back(path);
return;
}
if (current_sum > target || idx == n) return; // 剪枝
// 选当前元素
path.push_back(nums[idx]);
dfs(idx + 1, current_sum + nums[idx], target);
path.pop_back();
// 不选当前元素
dfs(idx + 1, current_sum, target);
}
1.3.4🌻总结
-
递归树结构不同:
- 固定长度问题的递归树深度固定,通过层数控制;
- 不固定长度问题的递归树深度动态,通过条件(如和、元素个数)控制。
-
参数设计逻辑不同:
- 固定长度问题依赖层数和起始下标;
- 不固定长度问题依赖元素下标和当前状态。
-
剪枝策略不同:
- 固定长度问题剪枝通常基于剩余元素数量;
- 不固定长度问题剪枝基于动态条件(如和超过目标值)。
1.3.5✌️延伸思考
遇到 DFS 问题时,可按以下步骤判断类型:
是否需要选满固定数量元素?
是 → 固定长度问题(层数参数);
否 → 不固定长度问题(下标参数)。是否有动态约束条件(如和为 k、元素个数最少)?
是 → 需在递归中维护状态并剪枝;
否 → 仅需控制组合不重复。是否允许元素重复使用?
是 → 递归时idx不变(如组合总和问题);
否 → 递归时idx+1(如子集问题)。
1.4 固定长度再分类:状态导向&问题导向📍📍📍
注意:非固定长度的问题都是状态导向的。
2.例题
2.1全排列
2.1.1题目描述
给定一个整数 nn,将数字 1∼n1∼n 排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 nn。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤7 1≤n≤7
输入样例:
3输出样例:
1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1
2.1.2解题思路

- 将问题抽象为空间树 : 如上图所示
- 剪枝
- 约束条件:在生成排列的过程中,每个元素只能使用一次。
- 剪枝实现:使用visited数组标记已选择的元素,若当前元素已被使用,则跳过该分支。
3.状态回退-->恢复现场
- 路径记录:移除最后选择的元素(通过
path.pop())。- 元素使用标记:将当前元素的使用状态重置为
False。
2.1.3代码展示
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
int path[10]; //记录路径
bool visited[10];//记录每个数的状态,有利于剪枝
int n;
void dfs(int u) {
if (u == n) { //如果u==n代表每个位置上都有数字了,输出对应的路径
for (int i = 0; i < n; i++) {
cout << path[i] << ' ';
}
cout << endl;
return;
}
for (int i = 1; i <= n; i++) {
if (!visited[i]) {
path[u] = i;
visited[i] = true;
dfs(u + 1);
//恢复现场
visited[i] = false;
path[u] = 0; //写不写都可以,因为path[u]每一次都会被覆盖掉
}
}
}
signed main() {
cin >> n;
dfs(0);//从第0个位置开始搜
return 0;
}
2.2组合数
2.2.1题目描述
从n个数中挑出m个数的组合,按字典序输出。其中相同的m个数算1个组合。
输入格式:
两个正整数n和m.(1≤m≤n≤10)
输出格式:
所有m个数的组合数,每行m个整数。
输入样例:
在这里给出一组输入。例如:
5 3输出样例:
在这里给出相应的输出。例如:
1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5
2.2.2解题思路
- 构建空间树(思路与上一题一致,不再赘述)
- 剪枝
规则:下一层起始点为
i+1,避免重复组合
代码:for (int i = start; i <= n; i++)
3.状态回退:移除path路径的最后一个选择
2.2.3代码展示
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<math.h>
#include<string>
#include <set>
#include<stack>
#include<vector>
#define int long long
using namespace std;
int n, m;
vector<int> path; // 改为动态初始化
void dfs(int u, int start) {
if (u == m) {
for (int num : path) { // 直接遍历path中的元素
cout << num << ' ';
}
cout << endl;
return;
}
for (int i = start; i <= n; i++) {
path.push_back(i); // 添加当前选择
dfs(u + 1, i + 1); // 递归,下一层从i+1开始
path.pop_back(); // 回溯,移除最后一个选择
}
}
signed main() {
cin >> n >> m;
dfs(0, 1); // 从u=0(已选0个元素),start=1(从数字1开始选)
return 0;
}
为了方便理解递归调用过程,下边以n=4,m=2举例,画出其对应的递归树:
递归深度 0: dfs(0,1)
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
↓ ↓ ↓ ↓
递归深度 1: 选1→dfs(1,2) 选2→dfs(1,3) 选3→dfs(1,4) 选4→dfs(1,5)
┌───┼───┐ ┌───┼ ┌───┐
↓ ↓ ↓ ↓ ↓ ↓
递归深度 2: 选2 选3 选4 选3 选4 选4
[1,2][1,3][1,4] [2,3][2,4] [3,4]
2.3指数型
2.3.1题目描述
从1∼n个数中选择任意多个,输出所有选择方案。
输入格式:
一个正整数n。
输出格式:
每行一种方案
没有选择任何数,输出空行
同一行数按字典顺序
输入样例:
在这里给出一组输入。例如:
3在这里给出相应的输出。例如:
1 1 2 1 2 3 1 3 2 2 3 3
2.3.2解题思路
- 构建空间树
- 剪枝函数
通过控制
start参数避免生成重复子集:
- 约束条件:每个子集中的元素必须按升序排列(如
[1,2]合法,[2,1]非法)。- 剪枝实现:递归时传入
i+1作为下一层的start,确保后续选择的数字大于当前数字。
3.状态回退
4.递归树展示
递归深度 0: dfs(1)
[]
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
↓ ↓ ↓
递归深度 1: 选1→dfs(2) 选2→dfs(3) 选3→dfs(4)
[1] [2] [3]
┌───┼───┐ ┌───┐
↓ ↓ ↓ ↓ ↓
递归深度 2: 选2 选3 选4 选3 选4
[1,2] [1,3] [2,3]
┌─┴─┐ ┌─┴─┐ ┌─┴─┐
↓ ↓ ↓ ↓ ↓ ↓
递归深度 3:选3 选4 选4 选4
[1,2,3] [1,2,4] [1,3,4] [2,3,4]
2.3.3 🌻对比学习(本题与全排列的不同之处)🌻
| 对比项 | 子集生成(本题) | 全排列 |
|---|---|---|
| 输出时机 | 每次递归进入时立即输出当前路径 | 仅当路径长度达到 n 时输出 |
| 解空间结构 | 子集树(每个节点代表一个子集) | 排列树(每个叶节点代表一个排列) |
| 路径约束 | 元素升序,不可重复 | 必须包含所有元素,顺序不同即不同解 |
| 剪枝策略 | 通过 start 参数控制后续选择范围 | 通过 visited 数组标记已使用元素 |
输出结果为什么会有这种差异?
- 子集问题:需要生成所有可能的子集(包括空集),因此每个中间状态都是有效的解,需要立即输出。
- 全排列问题:需要生成所有元素的排列,因此只有当路径包含所有元素时才是有效的解,需要达到固定深度后输出。
2.3.4代码展示
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n;
vector<int> path;
void dfs(int start) {
// 输出当前子集
for (int num : path) cout << num << ' ';
cout << endl;
// 从start开始尝试添加数字
for (int i = start; i <= n; i++) {
path.push_back(i);
dfs(i + 1); // 递归处理剩余数字,避免重复
path.pop_back(); // 回溯
}
}
int main() {
cin >> n;
dfs(1); // 从数字1开始
return 0;
}
2.4n-皇后问题
2.4.1解题思路
用固定长度DFS逐行放置皇后,每行选一列。通过标记列、主对角线(i+u)、副对角线(i-u+n)避免冲突。递归n层(每行一层),全放置成功时输出布局,利用回溯撤销状态。
2.4.3代码展示
#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
#define int long long
typedef long long ll;
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool col[N], dg[N], udg[N];
void dfs(int u) { //循环每一行(寻找放置皇后的合适位置)
if (u == n ) {
for (int i = 0; i < n; i++) {
puts(g[i]); //puts(g[i]) 用于输出棋盘的第 i 行内容
}
cout << endl;
}
for (int i = 0; i < n; i++) {
if (!col[i] && !dg[i + u] && !udg[i - u + n]) {
g[u][i] = 'Q';
col[i] = dg[i + u] = udg[i - u + n] = true;
dfs(u + 1);
//恢复现场
g[u][i] = '.';
col[i] = dg[i + u] = udg[i - u + n] = false;
}
}
}
signed main() {
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
g[i][j] = '.';
dfs(0);
return 0;
}
2.5飞机降落
2.5.1题目描述
N(N<10)架飞机准备降落到某只有一条跑道的机场。
第 i 架飞机在Ti时刻到达机场上空,到达时它的剩余油料还可继续盘旋Di个单位时间,降落过程需要Li单位时间。
一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。
请你判断 N架飞机是否可以全部安全降落,可以降落则输出降落顺序。
如果有多个可以安全降落的顺序,按字典顺序输出,每行一个。
输入格式:
第1行为1个正整数N
接下来N行,每行3个整数,分别是到达时刻Ti,盘旋时间Di,降落过程的时间Li
输出格式:
安全的降落顺序,每行1个,按字典顺序。如果没有则输出NO
输入样例:
在这里给出一组输入。例如:
3 0 100 10 10 10 10 0 2 20输出样例:
在这里给出相应的输出。例如:
3 2 1
2.5.2解题思路
核心思路:
当前的这架飞机可以加入path的条件-->其前一架飞机的降落时间在当前这架飞机的盘旋时间内。
2.5.3代码展示
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1e4 + 10;
int n;
struct plane {
int t; //到达时间
int d; //盘旋时间
int l; //下降时间
};
vector<plane> p(N);
vector<int> path(N); //记录飞机的下标
bool visited[N], flag;
void cal(int u ,int last){
if (u == n ) { //只有到达这一层才说明有方案,如果这一层都到达不了说明没有方案,因为在过程中会剪枝
flag = true;
for (int i = 0; i < n; i++) {
cout << path[i] << ' ';
}
cout << endl;
}
//last记录的是前一架飞机最晚降落到地面的时刻,如果这个时刻大于当前这架飞机的最晚降落时间则不可以加入
//这架飞机在空中停留的时刻范围:[飞机到达时刻 -- 飞机到达时刻+悬停时间]
for (int i = 1; i <= n; i++) {
if (!visited[i] && last <= p[i].t + p[i].d) {
visited[i] = true;
path[u] = i;
cal(u + 1, max(last, p[path[u]].t) + p[path[u]].l);
visited[i] = false;
}
}
}
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> p[i].t >> p[i].d >> p[i].l;
}
cal(0, 0);
if (!flag) cout << "NO";
return 0;
}
2.6 safecracker
2.6.1题目描述
给你一个target数和一串字符串,这串字符都是A~Z,并且规定A~Z分别代表1~26。要求从这一串字符中找出5个字符(而且要是按字典序排序最大的),使公式v−w2+x3−y4+z5=target成立,如果不存在则输出no solution.
输入格式:
输入一行,一个正整数target,一个由A~Z组成的字符串
输出格式:
字符串中的五个字母,能使公式成立,且字典序最大。
输入样例:
在这里给出一组输入。例如:
11700519 ZAYEXIWOVU输出样例:
在这里给出相应的输出。例如:
YOXUZ
2.6.2解题思路
输入处理与预处理
- 读取目标值
target和候选字符串str- 对字符串降序排序(如 "ABC" → "CBA"),确保优先尝试字典序大的字母组合
状态初始化
- 创建长度为 5 的路径数组
path存储当前组合- 使用布尔数组
used标记每个字母是否已被选择深度优先搜索(DFS)
- 递归函数
dfs(u):处理路径的第u个位置- 终止条件:当
u == 5时,检查当前组合是否满足表达式- 遍历候选:按降序遍历每个字母,选择未使用的字母并递归
- 恢复现场
剪枝优化
- 找到第一个有效解后立即终止搜索(利用字典序最大特性)
- 在计算表达式时使用整数幂替代浮点数函数,避免精度误差
2.6.3代码展示
#include <iostream>
#include <string>
#include <algorithm>
#include<cmath>
using namespace std;
string str;
int target;
bool used[26]; // 标记字母是否已被使用
string path; // 存储当前路径
bool found = false;
bool check() {
int res = 0;
for (int i = 0; i < 5; i++)
res += (i % 2 ? -1 : 1) * pow(path[i] - 'A' + 1, i + 1);
return res == target;
}
void dfs(int u) {
if (u == 5) {
if (check() && !found) {
cout << path << endl;
found = true;
}
return;
}
for (char c : str) { // 遍历降序排列后的字符
if (!used[c - 'A']) {
used[c - 'A'] = true;
path[u] = c;
dfs(u + 1);
if (found) return; // 提前终止📍📍📍
used[c - 'A'] = false;
}
}
}
int main() {
cin >> target >> str;
sort(str.rbegin(), str.rend()); // 直接降序排序
path.resize(5); // 预分配路径长度
dfs(0);
if (!found) cout << "no solution";
return 0;
}
string的sort函数补充:
- 正向排序:
sort(str.begin(), str.end())会将字符串按升序排列(如 "CBA" → "ABC")。- 反向排序:
sort(str.rbegin(), str.rend())会将字符串按降序排列(如 "ABC" → "CBA")。
2.7选数问题📍
2.7.1题目描述
给定若干个正整数a0、a0 、…、an-1 ,从中选出若干数,使它们的和恰好为k,
要求找选择元素个数最少的解。如果有多个最优解,输出字典序最小的。
输入格式:
输入有两行,第一行给出2个正整数n,k,用空格分隔。第二行是用空格分隔的n个整数。
输出格式:
输出有两行,第一行从小到大输出选择的元素,第二行输出元素的个数。
输入样例:
在这里给出一组输入。例如:
5 9 1 1 4 5 7输出样例:
在这里给出相应的输出。例如:
4 5 2
2.7.2解题思路
核心思路(非固定长度的变形)
- DFS 遍历:递归尝试每个元素的选 / 不选,生成所有可能组合。
- 剪枝优化:若当前和超过 k 或路径长度≥已知最优解,提前终止。
- 字典序控制:数组升序排序,递归时优先选小元素。
2.7.3代码展示
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<math.h>
#include<string>
#include <set>
#include<stack>
#include<vector>
#define int long long
using namespace std;
int n, k;
vector<int> a, cur, min_cur;
int min_len = 1e9; //初始化为较大的数,判断最后是否有解
void dfs(int idx, int sum, vector<int>& cur) {
if (sum == k) {
if (cur.size() < min_len || (cur.size() == min_len && cur < min_cur)) {
min_len = cur.size();
min_cur = cur;
}
}
//剪枝
if (sum > k || idx == n) return; //idx==n代表a数组元素已经遍历完毕了(a数组下标从0开始)
//选择当前数字
cur.push_back(a[idx]);
dfs(idx + 1, sum + a[idx], cur);
cur.pop_back();
//不选当前数字,sum不变,idx+1继续递归下一个数
dfs(idx + 1, sum, cur);
}
signed main() {
cin >> n >> k;
a.resize(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a.begin(), a.end());
dfs(0, 0, cur);
if (min_len != 1e9) {
for (int i = 0; i < min_cur.size(); i++) {
if (i) cout << ' '; //为了保证输出结果的前后都没有空格📍📍📍
cout << min_cur[i];
}
cout << endl << min_cur.size() << endl;
}
}
2.8最大团问题📍
2.8.1题目描述
给定图G(V, E),团是一个子图g(v, e),以至于对于v中的所有顶点对v1、v2,在e中都存在一条边(v1, v2)。最大团是具有最多顶点数的团。
输入格式:
输入包含多组测试。对于每组测试:第一行有一个整数n,表示顶点数。(1 < n <= 50)接下来的n行,每行有n个0或1,表示顶点i(行号)和顶点j(列号)之间是否存在边。当n = 0时,表示输入结束。此组测试不应处理。
输出格式:
每组测试输出一个数字,即最大团中的顶点数。
输入样例:
5 0 1 1 0 1 1 0 1 1 1 1 1 0 1 1 0 1 1 0 1 1 1 1 1 0 0输出样例:
4
2.8.2解题思路(与选数问题类似)
核心思路:非固定长度的DFS
- 首先这道题目是不固定长度的,所以递归每个点。
- 判断当前点是否可以成团的条件是看当前点和path中的点是否都有边连接,如果有连接则可以成团,递归有这个点。
- 每次递归进去后要先更新最大成团的数量和最大成团点的集合。
2.8.3代码展示
#include <iostream>
#include <vector>
using namespace std;
int n; // 节点数
int grid[50][50]; // 邻接矩阵
int max_size = 0; // 最大团的大小
vector<int> best_path; // 最大团的节点集合
// 检查当前节点是否可以加入团
bool check(int node, const vector<int>& path) {
for (int num : path) {
if (grid[node][num] == 0) return false;
}
return true;
}
// DFS函数:使用"选与不选"模板
void dfs(int idx, vector<int>& path) {
// 处理完所有节点,更新最大团
if (idx == n) {
if (path.size() > max_size) {
max_size = path.size();
best_path = path;
}
return;
}
// 不选当前节点,直接处理下一个节点
dfs(idx + 1, path);
// 选当前节点(需先检查是否满足条件)
if (check(idx, path)) {
path.push_back(idx);
dfs(idx + 1, path); // 递归处理下一个节点
path.pop_back(); // 回溯
}
}
signed main() {
while (cin >> n && n) {
// 初始化
max_size = 0;
best_path.clear();
// 输入邻接矩阵
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> grid[i][j];
}
}
// 开始DFS
vector<int> path;
dfs(0, path);
// 输出结果
cout << max_size << endl;
}
return 0;
}
2.9最佳组队问题
2.9.1题目描述
双人混合ACM程序设计竞赛即将开始,因为是双人混合赛,故每支队伍必须由1男1女组成。现在需要对n名男队员和n名女队员进行配对。由于不同队员之间的配合优势不一样,因此,如何组队成了大问题。
给定n×n优势矩阵P,其中P[i][j]表示男队员i和女队员j进行组队的竞赛优势(0<P[i][j]<10000)。设计一个算法,计算男女队员最佳配对法,使组合出的n支队伍的竞赛优势总和达到最大。输入格式:
测试数据有多组,处理到文件尾。每组测试数据首先输入1个正整数n(1≤n≤9),接下来输入n行,每行n个数,分别代表优势矩阵P的各个元素。
输出格式:
对于每组测试,在一行上输出n支队伍的竞赛优势总和的最大值。
输入样例:
3 10 2 3 2 3 4 3 4 5输出样例:
18
2.9.2解题思路
核心思路:本题按照固定长度的DFS解决
- 判断本题是固定长度的DFS,按照定长的模板每次递归每一层。
- 当层数为n时,更新最大竞争优势总和ma。
- 循环遍历每一个男生,如果没有被访问则可以与当前的女生u组队,继续递归。
2.9.3代码展示
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
#define int long long
using namespace std;
int n, ma = -1e9;
int grid[10][10];
bool visited[10];
void dfs(int u ,int cur) {
if (u == n) {
ma = max(ma, cur);
return;
}
for (int i = 0; i < n; i++) { //循环男生
if (!visited[i]) {
visited[i] = true;
dfs(u + 1, cur + grid[i][u]);
visited[i] = false;
}
}
}
signed main()
{
while (cin >> n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> grid[i][j]; //i表示男生,j表示女生
}
}
ma = -1e18;
dfs(0, 0);
cout << ma << endl;
}
return 0;
}
2.10 TSP问题 (固定长度的状态导向问题)📍
2.10.1题目描述
一个售货员需要在n个城市(编号1∼n)周游推销商品,假设从1号城市出发,给出n个城市之间旅行的开销矩阵,求周游全部城市一遍最少开销是多少。
输入格式:
第一行是城市个数n(1≤n≤20),后面是n×n的开销矩阵,开销cost取值范围[0,10000],如果两个城市不可达,cost为99999
输出格式:
第一行是最少开销
第二行是周游顺序,城市编号以空格隔开
如果无法周游,输出no solution
输入样例:
在这里给出一组输入。例如:
4 99999 3 6 7 12 99999 2 8 8 6 99999 2 3 7 6 99999输出样例:
在这里给出相应的输出。例如:
10 1 2 3 4
2.10.2代码展示
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>
#define int long long
using namespace std;
const int N = 15; // n最大10比较合适
int n, cost[N][N], mi = LLONG_MAX, path[N], best_path[N];
bool visited[N];
void dfs(int u, int cnt, int sum) {
if (cnt == n) { /* 因为有好几种不同的周游方式,
只有当周游的城市数量cnt == n
且可回到起始点
且总花费小于之前周游方式中的最小花费*/
if (cost[u][1] != 9999 && sum + cost[u][1] < mi) { // 检查是否能回到起点
mi = sum + cost[u][1];
// 保存最优路径
for (int i = 0; i < n; i++) {
best_path[i] = path[i];
}
}
return;
}
for (int i = 1; i <= n; i++) {
if (!visited[i] && cost[u][i] != 99999) {/*cost[u][i] != 99999是决定本题用状态导向的根本,
城市与城市之间存在关系,所以用状态导向!!*/
visited[i] = true;
path[cnt] = i;
dfs(i, cnt + 1, sum + cost[u][i]); /*状态导向与位置导向不同的根本之处在于递归参数,
本题是递归要访问的城市i*/
visited[i] = false;
}
}
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> cost[i][j];
visited[1] = true;
path[0] = 1;
dfs(1, 1, 0);
if (mi == LLONG_MAX) {
cout << "no solution" << endl;
}
else {
cout << mi << endl;
for (int i = 0; i < n; i++)
cout << best_path[i] << ' ';
}
return 0;
}
2.11 01背包问题📍
2.11.1 题目描述
有n个物品,每个物品i有重量w[i]和价值v[i],现在要选若干物品放入背包,背包最多能容纳重量为C,求能达到的最大价值。
输入格式:
第一行是物品个数n和背包容量C
接下来是n行,每行是一个物品的重量和对应价值
输出格式:
第一行是背包能达到的最大价值,第二行是所选物品编号,如果没有可选物品,输出 no solution
输入样例:
在这里给出一组输入。例如:
3 25 20 20 15 30 10 25输出样例:
在这里给出相应的输出。例如:
55 2 3
2.11.2 代码展示
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1e4 + 10;
int n, c, ma = -1e9;
struct bag {
int w;
int v;
};
vector<bag> b(N);
vector<int> path, best_path;
void dfs(int idx, int total_val, int total_weight) {
if (idx > n) {
if (total_val > ma) {
ma = total_val;
best_path = path;
}
return;
}
//不放这个物品
dfs(idx + 1, total_val, total_weight);
//放这个物品(有判断条件)
if (total_weight + b[idx].w <= c) {
path.push_back(idx);
dfs(idx + 1, total_val + b[idx].v, total_weight + b[idx].w);
path.pop_back();
}
}
signed main() {
cin >> n >> c;
for (int i = 1; i <= n; i++) {
cin >> b[i].w >> b[i].v;
}
dfs(1, 0, 0);
if (ma==0) {
cout << "no solution";
}
else {
cout << ma << endl;
for (int num : best_path) {
cout << num << ' ';
}
}
return 0;
}
2.12 图的着色问题📍
2.12.1 题目描述
给定无向连通图G和m种不同的颜色,用这些颜色为图G的各定点着色,每个顶点着一种颜色,是否有一种着色法使G中每条边的2个顶点着不同颜色。
输入格式:
第一行是顶点个数n及颜色数m,接下来n行是其邻接矩阵。
输出格式:
输出所有符合条件的着色方案
输入样例:
在这里给出一组输入。例如:
7 4 0 1 0 0 0 0 1 1 0 1 1 1 1 1 0 1 0 1 0 0 0 0 1 1 0 1 0 0 0 1 0 1 0 1 0 0 1 0 0 1 0 0 1 1 0 0 0 1 0输出样例:
在这里给出相应的输出。例如:
384
2.12.2 代码展示
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1e4 + 10;
int n, m, res, grid[N][N];
int color[N]; //第二个数组用来标记该点的颜色,这属于该点的状态。
void dfs(int u) {
if (u > n) {
res++;
return;
}
for (int c = 1; c <= m; c++) {
bool can = true;
for (int i = 1; i <= n; i++) {
if (grid[u][i] == 1 && color[i] == c){
can = false;
break;
}
}
if (can) {
color[u] = c;
dfs(u + 1);
color[u] = 0;
}
}
}
signed main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> grid[i][j];
}
}
dfs(1);
cout << res << endl;
return 0;
}
2260

被折叠的 条评论
为什么被折叠?



