dfs算法训练
洛谷题单
https://www.luogu.com.cn/training/972732

开数组的范围
1. int 全局二维
256MB = 256 × 1024 × 1024 = ≈ 6700 万 int
3000×3000 int:36MB ✅
4000×4000 int:64MB ✅
5000×5000 int:100MB ✅
8000×8000 int:256MB ❌ 刚好顶满
蓝桥全局 int 安全:≤ 5000×5000比赛里开到 3000×3000 完全稳
2. bool 全局二维
16000×16000 bool:256MB ❌ 顶满
10000×10000 bool:100MB ✅
5000×5000 bool:25MB ✅
全排列
输出 n 的全排列。
样例输入:
3
样例输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
使用递归
c++
#include<bits/stdc++.h>
#define int long long
#define lll __uint128_t
#define PII pair<int ,int>
#define endl '\n'
using namespace std;
#define yn(ans) printf("%s\n", (ans)?"Yes":"No");
#define YN(ans) printf("%s\n", (ans)?"YES":"NO");
#define REP(i, e) for (int i = 0; i < (e); ++i)
#define REP1(i, s, e) for (int i = (s); i <=(e); ++i)
#define TESTS int t; cin >> t; while (t--)
#define TEST
const int N=2e5+10,M=1e3+10,mod=1e9+7;
int a[N],b[N],c[N],pre[N];
//全排列
int n;
/**样例输入:
3
样例输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
**/
void dfs(int x){
if(x==n+1){
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return;
}
for(int i=1;i<=n;i++){
if(b[i]==0){
b[i]=1;
a[x]=i;
dfs(x+1);
a[x]=0;
b[i]=0;
}
}
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
dfs(1);
return 0;
}
使用next_permutation
方法一
主要还是去求这个,排列组合
c++
#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[100];
int main()
{
scanf("%d",&n);
int tot=1;
for(int i=1;i<=n;++i)
{
a[i]=i;
tot*=i;
}
for(int i=1;i<=tot;++i)
{
for(int j=1;j<=n;++j) printf(" %d",a[j]); //输出格式注意
next_permutation(a+1,a+n+1);
printf("\n");
}
return 0;
}
方法二
#include<bits/stdc++.h>
#define int long long
#define lll __uint128_t
#define PII pair<int ,int>
#define endl '\n'
using namespace std;
#define yn(ans) printf("%s\n", (ans)?"Yes":"No");
#define YN(ans) printf("%s\n", (ans)?"YES":"NO");
#define REP(i, e) for (int i = 0; i < (e); ++i)
#define REP1(i, s, e) for (int i = (s); i <=(e); ++i)
#define TESTS int t; cin >> t; while (t--)
#define TEST
const int N=2e5+10,M=1e3+10,mod=1e9+7;
int a[N],b[N],c[N],pre[N];
//全排列
int n;
/**样例输入:
3
样例输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
**/
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
a[i]=i;
}
do{
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
cout<<endl;
}while(next_permutation(a+1,a+n+1));
return 0;
}
选或者不选
#include<bits/stdc++.h>
using namespace std;
int n; // 物品个数
int a[25]; // 物品价值/重量等属性
bool vis[25]; // 标记选了哪些(可选)
// ========== 核心DFS模板 ==========
// k: 当前考虑第k个物品
// 其他参数:根据题目需要,如当前和、当前重量、当前价值等
void dfs(int k, int current_sum) {
if(k > n) { // 所有物品处理完毕
// 在这里处理最终答案
// 比如:更新最大/最小值,统计方案数,输出方案等
ans = max(ans, current_sum);
return;
}
// 选择1:不选第k个物品
vis[k] = false; // 标记未选(可选)
dfs(k + 1, current_sum); // 状态不变,继续下一个
// 选择2:选第k个物品
vis[k] = true; // 标记已选(可选)
dfs(k + 1, current_sum + a[k]); // 状态更新,继续下一个
// vis[k] = false; // 如果vis是全局数组,需要回溯
}
int main() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
dfs(1, 0); // 从第1个开始,初始状态
return 0;
}
常见变形
1. 求子集和等于target的方案数
void dfs(int k, int sum) {
if(k > n) {
if(sum == target) ans++;
return;
}
dfs(k + 1, sum); // 不选
dfs(k + 1, sum + a[k]); // 选
}
2. 求选k个数的最大/最小和
cpp
复制
cpp
void dfs(int pos, int cnt, int sum) { // cnt: 已选个数
if(cnt > k) return; // 剪枝:选多了
if(pos > n) {
if(cnt == k) ans = max(ans, sum);
return;
}
dfs(pos + 1, cnt + 1, sum + a[pos]); // 选
dfs(pos + 1, cnt, sum); // 不选
}
3. 记录具体方案(回溯)
cpp
复制
cpp
vector<int> path;
void dfs(int k, int sum) {
if(k > n) {
if(sum == target) {
for(int x : path) cout << x << " ";
cout << endl;
}
return;
}
// 不选
dfs(k + 1, sum);
// 选
path.push_back(a[k]);
dfs(k + 1, sum + a[k]);
path.pop_back(); // 回溯
}
4. 有重量限制(背包类)
cpp
复制
cpp
void dfs(int k, int weight, int value) {
if(weight > W) return; // 剪枝:超重
if(k > n) {
ans = max(ans, value);
return;
}
dfs(k + 1, weight, value); // 不选
dfs(k + 1, weight + w[k], value + v[k]); // 选
}
BFS模板 --- 最短路径(迷宫问题)
c++
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m; // 地图大小
char g[N][N]; // 地图:'.'表示空地,'#'表示障碍
int dist[N][N]; // 距离数组,-1表示未访问
int dx[4] = {-1, 0, 1, 0}; // 四个方向:上右下左
int dy[4] = {0, 1, 0, -1};
int bfs(int sx, int sy, int ex, int ey) {
memset(dist, -1, sizeof dist); // 初始化距离为-1(未访问)
queue<pair<int, int>> q;
dist[sx][sy] = 0; // 起点距离为0
q.push({sx, sy}); // 起点入队
while (q.size()) { // 队列不为空
auto t = q.front(); // 取队首
q.pop();
int x = t.first, y = t.second;
if (x == ex && y == ey) // 到达终点
return dist[x][y];
for (int i = 0; i < 4; i++) { // 遍历四个方向
int a = x + dx[i], b = y + dy[i];
// 边界检查 + 障碍检查 + 访问检查
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (g[a][b] == '#') continue; // 障碍物
if (dist[a][b] != -1) continue; // 已访问过
dist[a][b] = dist[x][y] + 1; // 距离+1
q.push({a, b}); // 新点入队
}
}
return -1; // 无法到达
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> g[i];
int res = bfs(0, 0, n-1, m-1); // 从左上到右下
cout << res << endl;
return 0;
}
DFS模板 --- 全排列/组合搜索
c++
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int n;
int path[N]; // 当前路径/方案
bool used[N]; // 标记数字是否被使用
// 全排列:从n个数中选n个,有顺序
void dfs_permutation(int u) { // u表示当前填到第几个位置
if (u == n) { // 填满了,输出方案
for (int i = 0; i < n; i++)
cout << path[i] << ' ';
cout << endl;
return;
}
for (int i = 1; i <= n; i++) { // 尝试每个数字
if (!used[i]) { // 如果i还没用过
used[i] = true; // 标记为使用
path[u] = i; // 填入当前位置
dfs_permutation(u + 1); // 递归填下一个位置
used[i] = false; // 回溯!恢复状态
}
}
}
// 组合:从n个数中选k个,无顺序(选法,不考虑排列)
void dfs_combination(int start, int k, int u) {
// u:当前选了几个, start:从哪个数开始选(避免重复)
if (u == k) {
for (int i = 0; i < k; i++)
cout << path[i] << ' ';
cout << endl;
return;
}
for (int i = start; i <= n; i++) { // 从start开始,避免选重复的
path[u] = i;
dfs_combination(i + 1, k, u + 1); // 下一个从i+1开始选
// 这里不需要used数组,因为通过start控制不重复
}
}
// DFS求连通块( Flood Fill )
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
char g[N][N];
bool vis[N][N];
void dfs_floodfill(int x, int y) {
vis[x][y] = true; // 标记访问
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (g[a][b] == '#') continue; // 不是目标区域
if (vis[a][b]) continue; // 访问过
dfs_floodfill(a, b); // 递归搜索连通块
}
}
int main() {
cout << "=== 全排列 ===" << endl;
n = 3;
dfs_permutation(0);
cout << "\n=== 组合 C(4,2) ===" << endl;
n = 4;
dfs_combination(1, 2, 0);
return 0;
}
记忆化搜索模板(DFS + DP)
c++
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m;
int g[N][N], f[N][N]; // f[i][j] 表示从(i,j)出发的最大值
int dfs(int x, int y) {
if (f[x][y] != -1) return f[x][y]; // 记忆化:算过直接返回
f[x][y] = 1; // 至少包含自己
// 四个方向搜索
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < n && b >= 0 && b < m && g[a][b] < g[x][y]) {
f[x][y] = max(f[x][y], dfs(a, b) + 1); // 能滑下去就+1
}
}
return f[x][y];
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> g[i][j];
memset(f, -1, sizeof f); // 初始化为-1表示未计算
int res = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
res = max(res, dfs(i, j));
cout << res << endl;
return 0;
}
B3621 枚举元组


思路
我们可以去使用dfs去枚举每一个位置,用a[pos]=i,去记录当前位的值
c++
void dfs(int pos){
if(pos==n+1){
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return;
}
for(int i=1;i<=k;i++){
a[pos]=i;
dfs(pos+1);
}
}
表示一个萝卜一个坑位的意思
a【pos】=i,表示地pos个位置是i
代码
c++
#include<bits/stdc++.h>
#define int long long
#define lll __uint128_t
#define PII pair<int ,int>
#define endl '\n'
using namespace std;
#define yn(ans) printf("%s\n", (ans)?"Yes":"No");//快速打印
#define YN(ans) printf("%s\n", (ans)?"YES":"NO");
#define REP(i, e) for (int i = 0; i < (e); ++i)
#define REP1(i, s, e) for (int i = (s); i <=(e); ++i)
#define TESTS int t; cin >> t; while (t--)
#define TEST
const int N=2e5+10,M=1e3+10,mod=1e9+7;
int a[N],b[N],c[N],pre[N];
int n;
int k;
void dfs(int pos){
if(pos==n+1){
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return;
}
for(int i=1;i<=k;i++){
a[pos]=i;
dfs(pos+1);
}
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
dfs(1);
return 0;
}
B3622 枚举子集


思路
因为我们发现每个位置都有两种状态,我们就可以去定义两种状态的,一个0,一个1,每次都有两个可能
所以我们只需要定义两个位置的状态
c++
void dfs(int pos,int biao){
if(biao==0){
s+='N';
}
if(biao==1){
s+='Y';
}
if(pos==n){
cout<<s<<endl;
s=s.substr(0,s.size()-1);//切除一下,相当于回塑
return;
}
dfs(pos+1,0);
dfs(pos+1,1);
s=s.substr(0,s.size()-1);//回溯
}
代码
c++
#include<bits/stdc++.h>
#define int long long
#define lll __uint128_t
#define PII pair<int ,int>
#define endl '\n'
using namespace std;
#define yn(ans) printf("%s\n", (ans)?"Yes":"No");//快速打印
#define YN(ans) printf("%s\n", (ans)?"YES":"NO");
#define REP(i, e) for (int i = 0; i < (e); ++i)
#define REP1(i, s, e) for (int i = (s); i <=(e); ++i)
#define TESTS int t; cin >> t; while (t--)
#define TEST
const int N=2e5+10,M=1e3+10,mod=1e9+7;
int a[N],b[N],c[N],pre[N];
string s;
int n;
//用数字来表示状态
void dfs(int pos,int biao){
if(biao==0){
s+='N';
}
if(biao==1){
s+='Y';
}
if(pos==n){
cout<<s<<endl;
s=s.substr(0,s.size()-1);//切除一下,相当于回塑
return;
}
dfs(pos+1,0);
dfs(pos+1,1);
s=s.substr(0,s.size()-1);
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
dfs(1,0);
dfs(1,1);
return 0;
}
B3623 枚举排列


思路
选择两个数组一个数组记录第几个位置选的是什么,第二个就是有没有被选过,被选过的我们不要,没被选择的我们才要,记得回塑
c++
void dfs(int pos){
if(pos==k+1){
for(int i=1;i<=k;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return;
}
for(int i=1;i<=n;i++){
if(b[i]==0){
b[i]=1;
}else{
continue;
}
a[pos]=i;
dfs(pos+1);
b[i]=0;
}
}
代码
c++
#include<bits/stdc++.h>
#define int long long
#define lll __uint128_t
#define PII pair<int ,int>
#define endl '\n'
using namespace std;
#define yn(ans) printf("%s\n", (ans)?"Yes":"No");//快速打印
#define YN(ans) printf("%s\n", (ans)?"YES":"NO");
#define REP(i, e) for (int i = 0; i < (e); ++i)
#define REP1(i, s, e) for (int i = (s); i <=(e); ++i)
#define TESTS int t; cin >> t; while (t--)
#define TEST
const int N=2e5+10,M=1e3+10,mod=1e9+7;
int a[N],b[N],c[N],pre[N];
int n,k;
void dfs(int pos){
if(pos==k+1){
for(int i=1;i<=k;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return;
}
for(int i=1;i<=n;i++){
if(b[i]==0){
b[i]=1;
}else{
continue;
}
a[pos]=i;
dfs(pos+1);
b[i]=0;
}
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
dfs(1);
return 0;
}
P10448 组合型枚举


思路
我们可以去设置,两个数值记录,有没有选过和到底有没有用过,然后我们枚举只要从上一个数中枚举就行了
c++
for(int i=a[pos-1]+1;i<=n;i++){//关键代码
if(b[i]==0){
b[i]=1;
}else{
continue;
}
a[pos]=i;
dfs(pos+1);
b[i]=0;
}
代码
c++
#include<bits/stdc++.h>
#define int long long
#define lll __uint128_t
#define PII pair<int ,int>
#define endl '\n'
using namespace std;
#define yn(ans) printf("%s\n", (ans)?"Yes":"No");//快速打印
#define YN(ans) printf("%s\n", (ans)?"YES":"NO");
#define REP(i, e) for (int i = 0; i < (e); ++i)
#define REP1(i, s, e) for (int i = (s); i <=(e); ++i)
#define TESTS int t; cin >> t; while (t--)
#define TEST
const int N=2e5+10,M=1e3+10,mod=1e9+7;
int a[N],b[N],c[N],pre[N];
int n,m;
void dfs(int pos){
if(pos==m+1){
for(int i=1;i<=m;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return;
}
for(int i=a[pos-1]+1;i<=n;i++){
if(b[i]==0){
b[i]=1;
}else{
continue;
}
a[pos]=i;
dfs(pos+1);
b[i]=0;
}
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
dfs(1);
return 0;
}
P2089 烤鸡

思路
c++
相当于每个位置只能放1,2,3,枚举的去,我们可以去计算当前的总数和,然后去枚举好就行了
但是会超时有的
void dfs(int pos,int sum1){
if(sum1>n)return;
if(pos==11&&sum1==n){
string s;
for(int i=1;i<=10;i++){
s+=to_string(a[i])+" ";
}
ans.push_back(s);
return;
}
for(int v=1;v<=3;v++){
a[pos]=v;
dfs(pos+1,sum1+v);
}
}
代码
c++
#include<bits/stdc++.h>
#define int long long
#define lll __uint128_t
#define PII pair<int ,int>
#define endl '\n'
using namespace std;
#define yn(ans) printf("%s\n", (ans)?"Yes":"No");//快速打印
#define YN(ans) printf("%s\n", (ans)?"YES":"NO");
#define REP(i, e) for (int i = 0; i < (e); ++i)
#define REP1(i, s, e) for (int i = (s); i <=(e); ++i)
#define TESTS int t; cin >> t; while (t--)
#define TEST
const int N=2e5+10,M=1e3+10,mod=1e9+7;
int a[N],b[N],c[N],pre[N];
int n;
vector<string>ans;
void dfs(int pos,int sum1){
if(sum1>n)return;
if(pos==11&&sum1==n){
string s;
for(int i=1;i<=10;i++){
s+=to_string(a[i])+" ";
}
ans.push_back(s);
return;
}
for(int v=1;v<=3;v++){
a[pos]=v;
dfs(pos+1,sum1+v);
}
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
dfs(1,0);
if(ans.size()==0){
cout<<0<<endl;
}else{
cout<<ans.size()<<endl;
for(auto&s : ans){
cout<<s<<endl;
}
}
return 0;
}

减枝
可以进行减枝
c++
if (n < 10 || n > 30) {
cout << "0\n";
return 0;
}
加一个这个代码

P15435[蓝桥杯 2025 国 Python B] 免费披萨
P15435 [蓝桥杯 2025 国 Python B\] 免费披萨 - 洛谷 (luogu.com.cn)](https://www.luogu.com.cn/problem/P15435)

### 思路
我们可以去枚举每个位置的数值然后,去进行dfs,尝试枚举每个可能,然后填充后再进行判断计算出gcd的
### 代码
```c++
#include
>g[i][j];
}
}
solve();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
cout << g[i][j] << (j < 3 ? ' ' : '\n');
}
}
return 0;
}
```
## B3862 图的遍历(简单版)
[B3862 图的遍历(简单版) - 洛谷](https://www.luogu.com.cn/problem/B3862#submit)

### 思路
思路有两种,一种是从最大的开始找本身开始找哪些可以到达这个的最大值,叫
### 反向图
```c++
#include