c++深度搜索讲解及例题

C++ 深度优先搜索(DFS)详解

深度优先搜索(Depth-First Search,简称 DFS)是一种 "先走到底、再回头" 的遍历 / 搜索算法,核心思想是优先沿着一条路径探索到底,直到无法前进时回溯,再选择其他未探索的分支。它在 C++ 中广泛应用于图遍历、组合枚举、迷宫求解、树的遍历等场景,实现方式主要有递归(最直观)和栈模拟(非递归)两种。

核心原理:

DFS 的递归实现本质是利用函数调用栈完成 "回溯",核心步骤可概括为:

  1. 选择路径:从当前节点选择一条未走过的路径前进;
  2. 递归探索:对新节点重复上述选择,直到触发 "终止条件"(如到达目标、越界、路径重复);
  3. 回溯恢复:探索完当前路径后,撤销当前选择(恢复状态),回到上一节点选择其他路径。
例题:

题目: 弹簧长廊

cpp 复制代码
### 描述
学校里有一条由 n 个房间首尾排成的长廊,房间编号为 1,2,...,n。小 A 从房间 1 出发,
目标是到达房间 n。

对于每个房间 i(1 ≤ i < n),都安装了一组弹簧装置。若当前站在房间 i,
则他可以一跃最多跳 aᵢ 步,也就是可以跳到下面任意一个房间:
i+1, i+2, ..., min(n, i+aᵢ)

其中 aᵢ 由输入给出;若 aᵢ = 0,则表示从房间 i 无法继续前进。

请你计算:从房间 1 到房间 n 一共有多少种不同的走法。由于答案可能很大,
请对 10⁹ + 7 取模后输出。

---

### 输入描述
第一行输入一个整数 n。
第二行输入 n−1 个整数 a₁,a₂,...,a_{n−1}。

数据范围:
- 1 ≤ n ≤ 3000
- 0 ≤ aᵢ ≤ 3000
- Σ_{i=1}^{n−1} aᵢ ≤ 20000

---

### 输出描述
输出一行,一个整数,表示不同走法对 10⁹ + 7 取模后的结果。

---

### 用例输入 1
6

### 用例输出 1
7

---

### 提示
样例解释:
所有合法走法分别是:
1. 1 → 2 → 3 → 4 → 5 → 6
2. 1 → 2 → 3 → 4 → 6
3. 1 → 2 → 4 → 5 → 6
4. 1 → 2 → 4 → 6
5. 1 → 2 → 5 → 6
6. 1 → 3 → 4 → 5 → 6
7. 1 → 3 → 4 → 6

因此答案为 7。

思路:使用深度搜索并使用记忆化缩减时间复杂度,注意每个地方都要多mod取余

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3010,mod=1e9+7;
ll n,a[maxn],vis[maxn],ans;
ll dfs(ll x){
    if(x==n)return 1;
    if(vis[x]!=-1)return vis[x]%mod;
    vis[x]=0;
    for(int i=1;i<=a[x];i++){
        vis[x]=(vis[x]+dfs(x+i))%mod;
    }
    return vis[x]%mod;
}
void sol(){
    cin>>n;
    for(int i=1;i<n;i++)cin>>a[i];
    memset(vis,-1,sizeof(vis));
    cout<<dfs(1)%mod<<endl;
}
int main(){
    sol();
    return 0;
}

题目:黄金矿工

cpp 复制代码
### 描述
给定一个由0和1组成的矿洞,共有n行m列。
- 1 表示这个格子里有 1 枚金币;
- 0 表示这个格子里没有金币。

如果两个有金币的格子在上下左右四个方向之一相邻,则它们属于同一个金币区域。
由于挖矿很费时间,你今天只能选择一个金币区域进入,然后挖走里面的全部金币。
请你求出最多能拿到多少枚金币。

---

### 输入描述
第一行输入两个整数,表示地图的行数和列数:n, m
接下来 n 行,每行一个长度为 m 的仅由 0 和 1 组成的字符串,表示整张地图。

数据范围:
1 <= n, m <= 200

---

### 输出描述
输出一个整数,表示只选择一个金币连通块时,最多能拿到的金币数。

---

### 用例输入 1
5 6
001100
001110
000010
110000
110100

### 用例输出 1
6

思路:和上一题《弹簧长廊》一样使用深搜,在dfs函数里加入对每个方向的递归深搜

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
ll n,m,ans,vis[205][205],cnt;
char s[205][205];
void dfs(ll x,ll y){
    if(x<1 || y<1 || x>n || y>m || s[x][y]=='0' || vis[x][y])return;
    cnt++;
    vis[x][y]=1;
    dfs(x-1,y);
    dfs(x+1,y);
    dfs(x,y-1);
    dfs(x,y+1);
}
void sol(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>s[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(!vis[i][j] && s[i][j]=='1'){
                cnt=0;
                dfs(i,j);
                ans=max(ans,cnt);
            }
        }
    }
    cout<<ans<<endl;
}
int main(){
    sol();
    return 0;
}

题目:坤坤打篮球

cpp 复制代码
### 描述
一天,坤坤穿着背带裤和朋友们出来打球。休息时,他们n个人坐成一圈,在玩一个游戏。
一开始球在坤坤(1号)手上,每一回合,拿着球的人都可以把球传给左边或者右边的人。
现在坤坤想知道,球传了m回合之后,又回到了自己手上的传球方式有多少种呢?
两种传球方法被视作不同的方法,当且仅当这两种方法中,
接到球的人按接球顺序组成的序列是不同的。
例如3个人传3回合,一共有2种不同方式,使得最后又传回1号:
- 1->2->3->1
- 1->3->2->1

---

### 输入描述
输入两个数,分别代表n和m,空格隔开。
其中 n≤30, m≤30。

---

### 输出描述
输出一个数,代表满足题意的传球方式数量。

---

### 用例输入 1
3 3

### 用例输出 1
2

思路:主要是计算出在一个圆中如何计算当前拿球的人是第几个

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=35;
ll n,m,vis[maxn][maxn];
ll dfs(ll id,ll k){//id:当前拿球的人 k:已经传了多少回合
    if(k==m)return id==1;
    if(vis[id][k]!=-1)return vis[id][k];
    vis[id][k]=0;
    vis[id][k]+=dfs(id%n+1,k+1);//传给右边的人
    vis[id][k]+=dfs((id-2+n)%n+1,k+1);//传给左边的人
    return vis[id][k];
}
void sol(){
    cin>>n>>m;
    memset(vis,-1,sizeof(vis));
    cout<<dfs(1,0)<<endl;
}
int main(){
    sol();
    return 0;
}

题目:坤坤打篮球2

cpp 复制代码
### 描述
一天,坤坤穿着背带裤和朋友们出来打球。休息时,他们n个人坐成一圈,在玩一个游戏。
一开始球在坤坤(1号)手上,每一回合,拿着球的人都可以把球传给左边或者右边的人。

已知每个人都有一个正整数得分 \(a_i\)。如果某次传球后,球传到了编号为 \(i\) 的人手中,
那么本次传球获得的得分就是 \(a_i\)。

现在坤坤想知道,球传了 \(k\) 回合之后,刚好回到自己手上时最大得分之和是多少?

注意:第 \(k\) 次传球后如果球回到了1号手中,那么这一次仍然要计入 \(a_1\) 的得分。

---

### 输入描述
第一行输入两个整数 \(n, k\)。
第二行输入 \(n\) 个正整数 \(a_1, a_2, \dots, a_n\)。

数据范围:
- \(2 \le n \le 10\)
- \(1 \le k \le 100\)
- \(1 \le a_i \le 10^9\)

---

### 输出描述
输出一个整数,表示答案。
如果不存在满足条件的传球序列,输出 -1。

---

### 用例输入 1
5 6
5 9 1 7 4

### 用例输出 1
42

---

### 用例输入 2
4 3
3 1 2 8

### 用例输出 2
-1

---

### 提示
样例一解释:
一种最优传球方案为:
\[1 \rightarrow 2 \rightarrow 1 \rightarrow 2 \rightarrow 1 \rightarrow 2 \rightarrow 1\]
对应得到的总分为:
\[a_2 + a_1 + a_2 + a_1 + a_2 + a_1 = 9 + 5 + 9 + 5 + 9 + 5 = 42\]
最后一次传球回到1号,仍然要计入 \(a_1\)。

思路:需要比上一题多用一个sum来计算最大得分和,记得要加上max来取最大值

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const  ll maxn=1e5;
ll n,k,a[maxn],vis[1001][1001];
ll dfs(ll id,ll step){//id:当前拿球的人,step:已经传了
    if(step==k)return id==1?0:-1e18;
    if(vis[id][step]!=-1)return vis[id][step];
    vis[id][step]=-1;
    ll sum=-1e18;//sum:当前传球方式的得分之和
    ll l=id%n+1,r=(id-2+n)%n+1;
    sum=max(sum,dfs(l,step+1)+a[l]);//传给左边的人
    sum=max(sum,dfs(r,step+1)+a[r]);//传给右边的人
    return vis[id][step]=sum;
}
void sol(){
    cin>>n>>k;
    for(ll i=1;i<=n;i++)cin>>a[i];
    memset(vis,-1,sizeof(vis));
    if(dfs(1,0)<0)cout<<-1<<endl;
    else cout<<dfs(1,0)<<endl;
}
int main(){
    sol();
    return 0;
}

题目:洛谷 P13015 [GESP202506 六级] 学习小组

cpp 复制代码
# P13015 [GESP202506 六级] 学习小组

## 题目背景

对应的选择、判断题:<https://ti.luogu.com.cn/problemset/1186>

## 题目描述

班主任计划将班级里的 $ n $ 名同学划分为若干个学习小组,每名同学都需要分入
某一个学习小组中。观察发现,如果一个学习小组中恰好包含 $ k $ 名同学,则该学习小组的
讨论积极度为 $ a_k $。

给定讨论积极度 $ a_1, a_2, \ldots, a_n $,请你计算将这 $ n $ 名同学划分为学习小组
的所有可能方案中,讨论积极度之和的最大值。

## 输入格式

第一行,一个正整数 $ n $,表示班级人数。

第二行,$ n $ 个非负整数 $ a_1, a_2, \ldots, a_n $,表示不同人数学习小组
的讨论积极度。

## 输出格式

输出共一行,一个整数,表示所有划分方案中,学习小组讨论积极度之和的最大值。

## 输入输出样例 #1

### 输入 #1

```
4
1 5 6 3
```

### 输出 #1

```
10
```

## 输入输出样例 #2

### 输入 #2
8
0 2 5 6 4 3 3 4

### 输出 #2
12

## 说明/提示

对于 $40\%$ 的测试点,保证 $1\le n\le 10$。

对于所有测试点,保证 $1\le n\le 1000$,$0\le a_i\le 10^4$。

思路:推导出状态转移方程

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1005;
ll n,a[maxn],vis[maxn];
ll dfs(int x){
    if(x==0)return 0;//分完了
    if(vis[x]!=-1)return vis[x];
    ll ans=-1e18;
    for(int i=1;i<=x;i++){
        ans=max(ans,dfs(x-i)+a[i]);//分出一个小组,剩下的继续分
    }
    return vis[x]=ans;//记忆化
}
void sol(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    memset(vis,-1,sizeof(vis));
    cout<<dfs(n)<<endl;
}
int main(){
    sol();
}

题目:aki大盗

cpp 复制代码
### 描述
一名经验丰富的大盗,打算洗劫一条街上的店铺。
这条街上一共有 N 家店铺,每家店中都有一些现金。大盗事先踩点得知,如果他同时洗劫
两家相邻的店铺,街上的报警系统就会自动启动,然后警察就会蜂拥而至。
作为会编程的你,为了帮助警察破案,请你计算下,这名大盗今晚最多可以盗窃多少现金。

---

### 输入描述
输入的第一行是一个整数 T,表示一共有 T 组数据。
接下来的每组数据,第一行是一个整数 N,表示一共有 N 家店铺。
第二行是 N 个被空格分开的正整数,表示每一家店铺中的现金数量。每家店铺中的现金数量
均不超过 1000。

数据范围与提示:
T ≤ 50, 1 ≤ N ≤ 100000

---

### 输出描述
对于每组数据,输出一行。该行包含一个整数,表示大盗在不惊动警察的情况下可以得到的现金数量。

---

### 用例输入 1
2
3
1 8 2
4
10 7 6 14

### 用例输出 1
8
24

思路:由于洗劫相邻的两家就会被发现,所以递归得写两种取最大值

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
ll t,n,a[maxn],vis[maxn];
ll dfs(ll x){
    if(x>n)return 0;
    if(vis[x]!=-1)return vis[x];
    vis[x]=-10;
    vis[x]=max(dfs(x+1),dfs(x+2)+a[x]);
    return vis[x];
}
void sol(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    memset(vis,-1,sizeof(vis));
    cout<<dfs(1)<<endl;
}
int main(){
    cin>>t;
    while(t--){
        sol();
    }
    return 0;
}
相关推荐
yu85939582 小时前
时延估计的互相关算法(MATLAB实现)
开发语言·算法·matlab
ou.cs2 小时前
c# SemaphoreSlim保姆级教程
开发语言·网络·c#
|_⊙2 小时前
红黑树 (C++)
开发语言·c++·学习
楼田莉子2 小时前
同步/异步日志系统:工具类以及日志的简单模块
linux·服务器·数据结构·c++
王老师青少年编程2 小时前
动态规划之【树形DP】第4课:树形DP应用案例实践3
c++·动态规划·dp·树形dp·csp·信奥赛·提高组
Fate_I_C2 小时前
Kotlin 内部类和嵌套类
java·开发语言·kotlin
昵称暂无12 小时前
低代码平台深度测评:OutSystems vs Mendix谁更胜一筹
开发语言·低代码
七点半7702 小时前
FFmpeg C++ AI视觉开发核心手册 (整合版)适用场景:视频流接入、AI模型预处理(抽帧/缩放/格式转换)、高性能算法集成。
c++·人工智能·ffmpeg
We་ct2 小时前
JS手撕:函数进阶 & 设计模式解析
开发语言·前端·javascript·设计模式·面试·前端框架