算法--DFS

目录

1.DFS

1.1核心思想

1.2适用场景

1.3问题分类

1.3.1固定长度组合问题

1.3.2不固定长度组合问题

1.3.3两类问题的代码模板对比

1.3.4总结 

1.3.5✌️延伸思考

2.例题

2.1全排列

 2.1.1题目描述

2.1.2解题思路 

2.1.3代码展示 

2.2组合数

2.2.1题目描述

2.2.2解题思路

2.2.3代码展示 

2.3指数型

2.3.1题目描述

2.3.2解题思路 

2.3.3 对比学习(本题与全排列的不同之处)

2.3.4代码展示

2.4n-皇后问题

2.4.1解题思路

2.4.3代码展示

2.5飞机降落

2.5.1题目描述

2.5.2解题思路

2.5.3代码展示

2.6 safecracker

2.6.1题目描述

2.6.2解题思路

2.6.3代码展示

2.7选数问题

2.7.1题目描述

2.7.2解题思路

2.7.3代码展示

2.8最大团问题

2.8.1题目描述

2.8.2解题思路(与选数问题类似)

2.8.3代码展示

2.9最佳组队问题

2.9.1题目描述

2.9.2解题思路

2.9.3代码展示


1.DFS

1.1核心思想

DFS的核心思想可以用 “尝试所有可能,逐步构建解,不满足条件则回退” 来概括。它本质是一种有策略的穷举搜索,通过剪枝状态回退机制高效地在解空间中寻找可行解。以下从三个维度深入解析其核心思想:

一、解空间树与决策路径

DFS将问题抽象为一棵解空间树,树的每个节点代表一个部分解(或决策状态):

  • 根节点:初始状态(尚未做出任何选择)。
  • 中间节点:已做出部分选择,尚未完成整个解。
  • 叶节点:完整解或无效解。

核心操作:从根节点出发,通过递归向下扩展路径(做出选择),若发现当前路径不可能通向有效解,则回溯到父节点(撤销选择),尝试其他分支。

二、剪枝函数:避免无效搜索

关键在于剪枝策略,通过两类函数判断路径有效性:

  1. 约束函数:判断当前路径是否满足问题的约束条件(如组合问题中元素是否重复)。
  2. 限界函数:判断当前路径是否可能产生最优解(常用于优化问题,如旅行商问题)。

剪枝效果:若某节点被判定为无效,直接跳过其所有子树,大幅减少搜索空间。
示例:在全排列问题中,若当前路径已包含元素2,则后续选择跳过2,避免生成重复排列。

三、状态回退:恢复现场的艺术

每次递归返回前,必须撤销当前选择,恢复到选择前的状态,确保后续分支不受影响。关键步骤

  1. 选择:在当前节点做出一个选择,进入下一层递归。
  2. 递归:处理子问题。
  3. 撤销:递归返回后,撤销之前的选择,尝试其他可能性。

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> result;
vector 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> result;
vector 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. 递归树结构不同

    • 固定长度问题的递归树深度固定,通过层数控制;
    • 不固定长度问题的递归树深度动态,通过条件(如和、元素个数)控制。
  2. 参数设计逻辑不同

    • 固定长度问题依赖层数起始下标
    • 不固定长度问题依赖元素下标当前状态
  3. 剪枝策略不同

    • 固定长度问题剪枝通常基于剩余元素数量;
    • 不固定长度问题剪枝基于动态条件(如和超过目标值)。
1.3.5✌️延伸思考

遇到 DFS 问题时,可按以下步骤判断类型:

  1. 是否需要选满固定数量元素
    是 → 固定长度问题(层数参数);
    否 → 不固定长度问题(下标参数)。

  2. 是否有动态约束条件(如和为 k、元素个数最少)?
    是 → 需在递归中维护状态并剪枝;
    否 → 仅需控制组合不重复。

  3. 是否允许元素重复使用
    是 → 递归时idx不变(如组合总和问题);
    否 → 递归时idx+1(如子集问题)。

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解题思路 

算法--DFS_第1张图片

  1. 将问题抽象为空间树 : 如上图所示
  2. 剪枝
  • 约束条件:在生成排列的过程中,每个元素只能使用一次。
  • 剪枝实现:使用visited数组标记已选择的元素,若当前元素已被使用,则跳过该分支。

     3.状态回退-->恢复现场

  • 路径记录:移除最后选择的元素(通过path.pop())。
  • 元素使用标记:将当前元素的使用状态重置为False

2.1.3代码展示 

#include
#include
#include
#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解题思路

  1. 构建空间树(思路与上一题一致,不再赘述)
  2. 剪枝

规则:下一层起始点为 i+1,避免重复组合
代码for (int i = start; i <= n; i++)

      3.状态回退:移除path路径的最后一个选择 

2.2.3代码展示 

#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#define int long long
using namespace std;

int n, m;
vector 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解题思路 

  1. 构建空间树
  2. 剪枝函数

通过控制 start 参数避免生成重复子集:

  • 约束条件:每个子集中的元素必须按升序排列(如 [1,2] 合法,[2,1] 非法)。
  • 剪枝实现:递归时传入 i+1 作为下一层的 start,确保后续选择的数字大于当前数字。

     3.状态回退

2.3.3 对比学习(本题与全排列的不同之处)

对比项 子集生成(本题) 全排列
输出时机 每次递归进入时立即输出当前路径 仅当路径长度达到 n 时输出
解空间结构 子集树(每个节点代表一个子集) 排列树(每个叶节点代表一个排列)
路径约束 元素升序,不可重复 必须包含所有元素,顺序不同即不同解
剪枝策略 通过 start 参数控制后续选择范围 通过 visited 数组标记已使用元素

输出结果为什么会有这种差异?

  • 子集问题:需要生成所有可能的子集(包括空集),因此每个中间状态都是有效的解,需要立即输出。
  • 全排列问题:需要生成所有元素的排列,因此只有当路径包含所有元素时才是有效的解,需要达到固定深度后输出。

2.3.4代码展示

#include 
#include 
#include 
using namespace std;

int n;
vector 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
#include
#include
#include
#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 
#include 
#include 
using namespace std;

struct plane {
    int t;
    int d;
    int l;
};
vector p(1000);
vector path(1000);
bool visited[1000] = { false };
int n, last;
bool hasSolution = false;

void dfs(int u) {
    if (u == n + 1) {  // 所有飞机都已安排降落
        hasSolution = true;
        for (int i = 1; i <= n; i++) {
            cout << path[i] << ' ';
        }
        cout << endl;
        return;
    }

    for (int i = 1; i <= n; i++) {
        if (!visited[i]) {
            int earliestStart = max(last, p[i].t);  // 最早可能的降落开始时间
            if (earliestStart <= p[i].t + p[i].d) {  // 检查是否在盘旋时间内
                visited[i] = true;
                path[u] = i;
                int prevLast = last;
                last = earliestStart + p[i].l;  // 更新last为当前飞机降落结束时间

                dfs(u + 1);

                last = prevLast;  // 回溯
                visited[i] = false;
            }
        }
    }
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> p[i].t >> p[i].d >> p[i].l;
    }
    last = 0;  // 初始化last为0
    dfs(1);

    if (!hasSolution) {
        cout << "NO" << endl;
    }

    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解题思路

  1. 输入处理与预处理

    • 读取目标值 target 和候选字符串 str
    • 对字符串降序排序(如 "ABC" → "CBA"),确保优先尝试字典序大的字母组合
  2. 状态初始化

    • 创建长度为 5 的路径数组 path 存储当前组合
    • 使用布尔数组 used 标记每个字母是否已被选择
  3. 深度优先搜索(DFS)

    • 递归函数 dfs(u):处理路径的第 u 个位置
    • 终止条件:当 u == 5 时,检查当前组合是否满足表达式
    • 遍历候选:按降序遍历每个字母,选择未使用的字母并递归
    • 恢复现场
  4. 剪枝优化

    • 找到第一个有效解后立即终止搜索(利用字典序最大特性)
    • 在计算表达式时使用整数幂替代浮点数函数,避免精度误差

2.6.3代码展示

#include 
#include 
#include 
#include  
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解题思路

核心思路(非固定长度的变形)

  1. DFS 遍历:递归尝试每个元素的选 / 不选,生成所有可能组合。
  2. 剪枝优化:若当前和超过 k 或路径长度≥已知最优解,提前终止。
  3. 字典序控制:数组升序排序,递归时优先选小元素。

2.7.3代码展示

#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#define int long long
using namespace std;

int n, k;
vector a, cur, min_cur;
int min_len = 1e9;  //初始化为较大的数,判断最后是否有解

void dfs(int idx, int sum, vector& 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

  1. 首先这道题目是不固定长度的,所以递归每个点。
  2. 判断当前点是否可以成团的条件是看当前点和path中的点是否都有边连接,如果有连接则可以成团,递归有这个点。
  3. 每次递归进去后要先更新最大成团的数量和最大成团点的集合。

2.8.3代码展示

#include 
#include 
using namespace std;

int n;                  // 节点数
int grid[50][50];       // 邻接矩阵
int max_size = 0;       // 最大团的大小
vector best_path;  // 最大团的节点集合

// 检查当前节点是否可以加入团
bool check(int node, const vector& path) {
    for (int num : path) {
        if (grid[node][num] == 0) return false;
    }
    return true;
}

// DFS函数:使用"选与不选"模板
void dfs(int idx, vector& 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 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

输入格式:

测试数据有多组,处理到文件尾。每组测试数据首先输入1个正整数n(1≤n≤9),接下来输入n行,每行n个数,分别代表优势矩阵P的各个元素。

输出格式:

对于每组测试,在一行上输出n支队伍的竞赛优势总和的最大值。

输入样例:

3
10 2 3
2 3 4
3 4 5

输出样例:

18

2.9.2解题思路

核心思路:本题按照固定长度的DFS解决

  1. 判断本题是固定长度的DFS,按照定长的模板每次递归每一层。
  2. 当层数为n时,更新最大竞争优势总和ma。
  3. 循环遍历每一个男生,如果没有被访问则可以与当前的女生u组队,继续递归。

2.9.3代码展示

#include
#include
#include
#include
#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;
}

你可能感兴趣的:(基础算法,算法,dfs)