算法5: 搜索与回溯算法

零.【摘要】:在这个专刊里,我会把所有算法都讲一遍,这章讲了搜索与回溯算法的原理和题目。

一.【搜索与回溯算法】:

搜索和回溯算法都是常用的问题求解方法,经常用于解决组合优化问题,如全排列、子集、组合等。以下是搜索和回溯算法的基本思想和示例代码:

  1. 搜索算法的基本思想是通过遍历搜索问题的所有可能解,找到满足条件的解。搜索算法可以使用深度优先搜索(DFS)或广度优先搜索(BFS)来实现。

  2. 回溯算法是一种特殊的深度优先搜索算法,在搜索过程中,当发现当前的部分解不能满足问题的条件时,就会回溯到上一个状态,然后进行其他的尝试。回溯算法通常使用递归来实现。

二.【搜索之广度优先搜索】:

广度优先搜索(BFS,Breadth-First Search)是一种用于图和树等数据结构中的搜索算法。它从开始节点开始,逐层遍历,先访问离起始节点最近的节点,然后逐渐扩展到离起始节点更远的节点。

三【广度优先搜索的做法】:

  1. 创建一个队列(可以使用STL中的 queue)和一个visited数组(用于记录已经访问过的节点)。
  2. 将起始节点放入队列,并将其标记为已访问。
  3. 循环执行以下步骤,直到队列为空:
    • 从队列中取出队首节点。
    • 处理当前节点(例如,输出、记录或进行其他操作)。
    • 遍历当前节点的邻居节点:
      • 如果邻居节点未被访问过,则将其标记为已访问,并将其放入队列中。
  4. 完成搜索。

广度优先搜索的特点是按照节点的层次进行遍历,从起始节点开始,依次访问离起始节点最近的节点,然后再访问离起始节点较远的节点。

四.【广度优先搜索事例代码】:

#include <iostream>
#include <queue>
#include <vector>

using namespace std;

void bfs(vector<vector<int>>& graph, int start) {
    int n = graph.size();
    vector<bool> visited(n, false); // 记录已经访问过的节点
    queue<int> q; // 用队列来实现广度优先搜索

    visited[start] = true;
    q.push(start);

    while (!q.empty()) {
        int node = q.front();
        q.pop();
        cout << node << " "; // 打印当前节点

        // 遍历当前节点的邻居节点
        for (int i = 0; i < graph[node].size(); i++) {
            int neighbor = graph[node][i];
            if (!visited[neighbor]) {
                visited[neighbor] = true;
                q.push(neighbor);
            }
        }
    }
}

int main() {
    // 示例图的邻接表表示
    vector<vector<int>> graph = {
        {1, 2},
        {0, 3, 4},
        {0, 5},
        {1},
        {1},
        {2}
    };

    bfs(graph, 0); // 从0开始进行广度优先搜索

    return 0;
}

输出结果为:0 1 2 3 4 5。

在示例代码中,我们使用一个 visited 数组来记录已经访问过的节点,避免重复访问同一个节点。同时使用一个队列 q 来保存待访问的节点。我们从起始节点开始,将其标记为已访问并入队,然后循环进行弹出队首节点并打印,同时将其未访问的邻居节点标记为已访问并入队,直到队列为空。这样就完成了广度优先搜索的图遍历。

五.【搜索之深度优先搜索】:

深度优先搜索(DFS,Depth-First Search)是一种用于图和树等数据结构中的搜索算法。它从起始节点开始,通过依次访问节点的子节点,直到达到最深的节点,然后再回溯到上一层继续进行搜索。

六.【深度优先搜索的做法】:

  1. 创建一个visited数组(用于记录已经访问过的节点)。
  2. 从起始节点开始,调用递归函数进行深度优先搜索。
  3. 在递归函数中,首先处理当前节点(例如,输出、记录或进行其他操作)。
  4. 遍历当前节点的邻居节点:
    • 如果邻居节点未被访问过,则将其标记为已访问,并继续递归调用深度优先搜索函数。
  5. 完成搜索。

深度优先搜索的特点是优先访问离起始节点较远的节点,直到达到最深的节点,然后再回溯到上一层继续搜索。这种搜索方式类似于前序遍历树的方式。

七.【深度优先搜索事例代码】:

下面是一个使用深度优先搜索进行图遍历的示例代码:

#include <iostream>
#include <vector>

using namespace std;

void dfs(vector<vector<int>>& graph, vector<bool>& visited, int node) {
    visited[node] = true;
    cout << node << " "; // 处理当前节点

    // 遍历当前节点的邻居节点
    for (int i = 0; i < graph[node].size(); i++) {
        int neighbor = graph[node][i];
        if (!visited[neighbor]) {
            dfs(graph, visited, neighbor); // 递归调用进行深度优先搜索
        }
    }
}

void dfsTraversal(vector<vector<int>>& graph, int start) {
    int n = graph.size();
    vector<bool> visited(n, false); // 记录已经访问过的节点

    dfs(graph, visited, start); // 从起始节点开始进行深度优先搜索
}

int main() {
    // 示例图的邻接表表示
    vector<vector<int>> graph = {
        {1, 2},
        {0, 3, 4},
        {0, 5},
        {1},
        {1},
        {2}
    };

    dfsTraversal(graph, 0); // 从0开始进行深度优先搜索

    return 0;
}

输出结果为:0 1 3 4 2 5。

在示例代码中,我们使用一个 visited 数组来记录已经访问过的节点,避免重复访问同一个节点。我们从起始节点开始,调用递归函数进行深度优先搜索。在递归函数中,首先处理当前节点,然后遍历当前节点的邻居节点,如果邻居节点未被访问过,则将其标记为已访问并继续递归调用深度优先搜索函数。这样就完成了深度优先搜索的图遍历。

八.【搜索之回溯】:

回溯(Backtracking)是一种在搜索算法中常用的技术,用于在问题的解空间中进行搜索并找到所有满足特定条件的解。它通常用于解决组合、排列、子集等相关问题。

九.【回溯的做法】:

  1. 定义一个解空间,即问题的可能解集合。
  2. 定义一个候选集,表示当前选择的候选项(可能是下一步可以选择的元素)。
  3. 使用递归函数进行回溯搜索:
    • 终止条件:
      • 如果满足特定条件(例如,找到了一个有效解),则记录当前解。
    • 在候选集中选择一个元素作为下一步的选择,并将其添加到当前解中。
    • 递归调用回溯函数,进行下一步的搜索。
    • 撤销当前选择,恢复到上一层的状态,继续搜索下一个候选项。
  4. 返回所有找到的解。

十.【回溯事例代码】:
 

下面是一个使用回溯算法求解全排列问题的示例代码:

#include <iostream>
#include <vector>

using namespace std;

void backtrack(vector<int>& nums, vector<int>& path, vector<bool>& used, vector<vector<int>>& result) {
    // 终止条件:当当前排列的长度达到原始数组的长度时,将当前排列加入到结果集中
    if (path.size() == nums.size()) {
        result.push_back(path);
        return;
    }

    for (int i = 0; i < nums.size(); i++) {
        if (!used[i]) {
            used[i] = true;
            path.push_back(nums[i]);
            backtrack(nums, path, used, result);
            path.pop_back();
            used[i] = false;
        }
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> result;
    vector<int> path;
    vector<bool> used(nums.size(), false);

    backtrack(nums, path, used, result);

    return result;
}

int main() {
    vector<int> nums = {1, 2, 3};
    vector<vector<int>> result = permute(nums);

    // 输出所有排列
    for (int i = 0; i < result.size(); i++) {
        for (int j = 0; j < result[i].size(); j++) {
            cout << result[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

输出结果为:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

在示例代码中,我们使用回溯算法求解全排列问题。首先定义了一个 backtrack 函数,它使用递归方式进行回溯搜索。在每一层的搜索中,我们遍历候选集中的所有元素,并选择一个未使用过的元素,将其添加到当前排列中,并继续递归搜索下一个位置。当排列的长度达到原始数组的长度时,我们将当前排列加入到结果集中。每次搜索完成后,我们撤销当前选择,并将其标记为未使用,然后继续搜索下一个候选项,直到搜索完所有的元素。最后返回所有找到的排列。

三.【操作步骤】

1. 定义问题:明确问题的输入、输出和约束条件。确定问题的定义和目标。

2. 设计数据结构:根据问题的特性,选择适当的数据结构来表示问题的状态和解空间。例如,使用数组、链表、树等来存储问题的状态。

3. 实现搜索函数:编写一个搜索函数,该函数接收当前状态和其他必要的参数,并返回下一步要探索的状态。搜索函数应根据问题的定义和约束条件进行适当的状态转换。

4. 实现回溯函数:编写一个回溯函数,该函数用于在搜索过程中回溯到上一个状态。回溯函数应该撤销前一步的操作,以便重新探索其他可能的路径。

5. 实现终止条件:确定搜索过程的终止条件。这可以是找到了满足问题定义的解,或者超过了一定的时间或深度限制。

6. 执行搜索:从初始状态开始,调用搜索函数进行搜索。在搜索的过程中,根据问题的约束条件和终止条件,进行状态转换和回溯操作。

7. 处理结果:根据搜索的结果,处理和输出满足问题定义的解。如果没有找到解,可以输出相应的提示信息。

需要注意以下几点:

- 确保正确性:在实现过程中,确保搜索和回溯函数的正确性。可以使用调试工具和单元测试来验证算法的正确性。

- 优化性能:对于复杂的问题,可能需要考虑优化搜索算法的性能。可以使用剪枝、启发式搜索等技术来提高搜索效率。

- 处理大规模问题:对于大规模问题,可能需要考虑使用适当的数据结构和算法来降低内存和时间的消耗。

这些步骤是一个一般性的指导,具体的实现过程会因问题的不同而有所差异。在实际应用中,可以根据具体问题的需求进行适当的调整和修改。

四.【题目讲解】:

1:自然数的拆分


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 32931     通过数: 19305

【题目描述】

任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。

当n=7共14种拆分方法:

7=1+1+1+1+1+1+1
7=1+1+1+1+1+2
7=1+1+1+1+3
7=1+1+1+2+2
7=1+1+1+4
7=1+1+2+3
7=1+1+5
7=1+2+2+2
7=1+2+4
7=1+3+3
7=1+6
7=2+2+3
7=2+5
7=3+4
total=14

【输入】

输入n。

【输出】

按字典序输出具体的方案。

【输入样例】

7

【输出样例】

7=1+1+1+1+1+1+1
7=1+1+1+1+1+2
7=1+1+1+1+3
7=1+1+1+2+2
7=1+1+1+4
7=1+1+2+3
7=1+1+5
7=1+2+2+2
7=1+2+4
7=1+3+3
7=1+6
7=2+2+3
7=2+5
7=3+4

提交 统计信息 提交记录

【算法思路】:

我们可以使用回溯法来解决这个问题。

回溯法的基本思路是不断尝试不同的拆分方式,然后递归地处理剩下的数。

具体步骤如下:

  1. 定义一个函数backtracking,参数为当前要拆分的数n和当前拆分方案cur_solution。

  2. 如果当前拆分方案的和等于n,将其加入到结果集中。

  3. 否则,从1到n-1遍历所有可能的拆分数:

    • 将当前拆分数加入到拆分方案中。

    • 调用backtracking函数处理剩下的数(即n-当前拆分数)。

    • 从拆分方案中移除当前拆分数,以便尝试其他可能的拆分。

  4. 按字典序输出结果集中的拆分方案。
    【代码实现】:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<vector<int>> solutions;

void backtracking(int n, vector<int>& cur_solution) {
    // 当前拆分方案的和等于n,将其加入到结果集中
    if (n == 0) {
        solutions.push_back(cur_solution);
        return;
    }
    
    // 遍历所有可能的拆分数
    for (int i = 1; i <= n-1; i++) {
        // 将当前拆分数加入到拆分方案中
        cur_solution.push_back(i);
        
        // 递归处理剩下的数
        backtracking(n - i, cur_solution);
        
        // 从拆分方案中移除当前拆分数
        cur_solution.pop_back();
    }
}

int main() {
    int n;
    cin >> n;
    
    vector<int> cur_solution;
    backtracking(n, cur_solution);
    
    sort(solutions.begin(), solutions.end());
    
    // 输出结果
    for (const auto& solution : solutions) {
        cout << n << "=";
        for (int i = 0; i < solution.size(); i++) {
            cout << solution[i];
            if (i < solution.size() - 1) {
                cout << "+";
            }
        }
        cout << endl;
    }
    
    return 0;
}

2:八皇后


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 21432     通过数: 13194

【题目描述】

会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 × 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。

对于某个满足要求的8皇后的摆放方法,定义一个皇后串a与之对应,即a=b1b2...b8,其中bi为相应摆法中第i行皇后所处的列数。已经知道8皇后问题一共有92组解(即92个不同的皇后串)。

给出一个数b,要求输出第b个串。串的比较是这样的:皇后串x置于皇后串y之前,当且仅当将x视为整数时比y小。

【输入】

第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数b(1≤b≤92)。

【输出】

输出有n行,每行输出对应一个输入。输出应是一个正整数,是对应于b的皇后串。

【输入样例】

2
1
92

【输出样例】

15863724
84136275

提交 统计信息

【算法思路】

  1. 使用回溯法解决八皇后问题。
  2. 定义一个大小为8的数组queens,数组中的每个元素表示第i行皇后所在的列数。
  3. 定义一个递归函数backtracking来尝试放置皇后,参数为当前处理的行号row。
  4. 如果row等于8,表示已经找到了一组合法的解,将其转化为整数形式,并判断是否为目标解。
  5. 否则,从列1到8遍历当前行可放置皇后的位置,如果位置合法,则将该位置加入queens数组,然后递归调用backtracking处理下一行。
  6. 递归调用结束后,需要回溯,将queens数组当前位置的元素重置为0,以便尝试下一个位置。
  7. 不断重复步骤4~6,直到找到所有的解或达到目标解的编号b为止。
  8. 输出对应的皇后串。
     

【代码实现】:

#include <iostream>
#include <vector>
using namespace std;

vector<int> queens(8); // 存储皇后所在的列数
vector<int> solution; // 存储结果
int count = 0; // 计数变量,记录找到的解的数量

// 判断当前位置(row, col)能否放置皇后
bool isSafe(int row, int col) {
    // 检查当前列是否有其他皇后
    for (int i = 0; i < row; i++) {
        if (queens[i] == col) {
            return false;
        }
    }
    // 检查左上方对角线是否有其他皇后
    for (int i = row-1, j = col-1; i >= 0 && j >= 0; i--, j--) {
        if (queens[i] == j) {
            return false;
        }
    }
    // 检查右上方对角线是否有其他皇后
    for (int i = row-1, j = col+1; i >= 0 && j < 8; i--, j++) {
        if (queens[i] == j) {
            return false;
        }
    }
    // 当前位置安全
    return true;
}

// 回溯法尝试放置皇后
void backtracking(int row) {
    // 找到一组合法解,转化为整数形式并判断是否为目标解
    if (row == 8) {
        count++;
        if (count == b) {
            int num = 0;
            for (int i = 0; i < 8; i++) {
                num = num * 10 + queens[i] + 1;
            }
            solution.push_back(num);
        }
        return;
    }
    // 尝试放置皇后
    for (int col = 0; col < 8; col++) {
        if (isSafe(row, col)) {
            queens[row] = col;
            backtracking(row + 1);
            // 回溯,重置当前位置
            queens[row] = 0;
        }
    }
}

int main() {
    int n;
    cin >> n;
    while (n--) {
        int b;
        cin >> b;
        // 重置变量
        queens.assign(8, 0);
        solution.clear();
        count = 0;
        // 求解
        backtracking(0);
        // 输出结果
        for (int i = 0; i < solution.size(); i++) {
            cout << solution[i] << endl;
        }
    }
    return 0;
}

这段代码实现了求解八皇后问题的主要逻辑。在主函数中,首先读入测试数据的组数n,然后进行n次循环,每次循环读入一个正整数b,表示要求解的第b个皇后串。接着调用backtracking函数求解八皇后问题,并将结果输出。

注意,这段代码没有包含输入输出的部分,只实现了求解八皇后问题的核心算法。在实际使用时,需要根据题目要求进行输入输出的处理。

3:单词接龙


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 11168     通过数: 6661

【题目描述】

单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如beast和astonish,如果接成一条龙则变为beastonish,另外相邻的两部分不能存在包含关系,例如at和atide间不能相连。

【输入】

输入的第一行为一个单独的整数n(n<=20)表示单词数,以下n行每行有一个单词(只含有大写或小写字母,长度不超过20),输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。

【输出】

只需输出以此字母开头的最长的“龙”的长度。

【输入样例】

5
at
touch
cheat
choose
tact
a

【输出样例】

23

提交 统计信息 提交记录

【算法思路】: 

  1. 我们可以将单词为图的遍历问题。
  2. 首先构建一个图,每个单词作为一个节点,如果两个单词可以接在一起,则在两个节点之间连接一条边。例如,单词"at"和"touch"可以接在一起,它们的连接方式为"atouch"。
  3. 接下来,使用深度优先搜索(DFS)或广度优先搜索(BFS)遍历这个图,从给定字母开头的节点开始搜索,找出以该节点为起点的最长路径。
  4. 在搜索过程中,需要记录已经访问过的节点,以避免重复访问,并限制每个单词最多在路径中出现两次。
  5. 搜索结束后,找到最长路径的长度,并输出结果。

【代码实现】:

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

unordered_map<string, vector<string>> graph; // 存储单词图
unordered_map<string, int> visited; // 记录节点的访问次数

// 判断两个单词是否可以接在一起
bool canConnect(const string& word1, const string& word2) {
    int len = word1.length();
    return (word1.substr(len-2) != word2.substr(0, 2));
}

// 构建单词图
void buildGraph(const vector<string>& words) {
    int n = words.size();
    for (int i = 0; i < n; i++) {
        graph[words[i]] = vector<string>();
        visited[words[i]] = 0;
    }
    for (int i = 0; i < n-1; i++) {
        for (int j = i+1; j < n; j++) {
            if (canConnect(words[i], words[j])) {
                graph[words[i]].push_back(words[j]);
                graph[words[j]].push_back(words[i]);
            }
        }
    }
}

// 深度优先搜索找到以某个单词为起点的最长路径
int DFS(const string& word, int length) {
    if (visited[word] == 2) {
        return length;
    }
    visited[word]++;
    int maxLength = 0;
    for (const string& nextWord : graph[word]) {
        if (visited[nextWord] < 2) {
            int len = DFS(nextWord, length + nextWord.length() - 2);
            maxLength = max(maxLength, len);
        }
    }
    visited[word]--;
    return maxLength;
}

int main() {
    int n;
    cin >> n;
    vector<string> words(n);
    for (int i = 0; i < n; i++) {
        cin >> words[i];
    }
    string start;
    cin >> start;
    
    buildGraph(words);
    int maxLength = 0;
    for (const string& word : words) {
        if (word[0] == start[0]) {
            maxLength = max(maxLength, DFS(word, word.length() - 2));
        }
    }
    cout << maxLength << endl;
    
    return 0;
}

4:【例5.2】组合的输出


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 45855     通过数: 22910

【题目描述】

排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r≤n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。

现要求你用递归的方法输出所有组合。

例如n=5,r=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

【输入】

一行两个自然数n、r(1<n<21,1≤r≤n)。

【输出】

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。

【输入样例】

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

提交 统计信息 提交记录

 【算法思路】:

  1. 可以使用递归的方法输出所有组合。
  2. 递归的终止条件是当已经选取的元素数量等于需要选取的数量时,输出当前组合。
  3. 递归的过程是,对于当前可选元素的范围,依次选择一个元素加入组合中,然后递归调用函数选择剩下的元素,直到选择的元素数量达到需要选择的数量。
  4. 在递归调用之前,将已经选择的元素放入当前组合中,并传递已选择的元素的数量作为参数。
  5. 在递归调用之后,从当前组合中移除最后一个加入的元素,以便进行下一轮的选择。

【代码实现】:

#include <iostream>
#include <vector>
using namespace std;

void combination(vector<int>& nums, vector<int>& currComb, int startPos, int count) {
    if (count == 0) { // 终止条件:已经选取了需要选择的数量
        for (int num : currComb) {
            cout << num << " ";
        }
        cout << endl;
        return;
    }
    for (int i = startPos; i < nums.size(); i++) { // 选择当前元素,并递归选择剩下的元素
        currComb.push_back(nums[i]);
        combination(nums, currComb, i+1, count-1);
        currComb.pop_back();
    }
}

int main() {
    int n, r;
    cin >> n >> r;
    
    vector<int> nums(n);
    for (int i = 0; i < n; i++) {
        nums[i] = i + 1; // 将自然数1~n存入数组
    }
    vector<int> currComb;
    combination(nums, currComb, 0, r);
    
    return 0;
}

5:红与黑


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 37425     通过数: 15413

【题目描述】

有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

【输入】

包括多组数据。每组数据的第一行是两个整数W和H,分别表示x方向和y方向瓷砖的数量。W和H都不超过20。在接下来的H行中,每行包括W个字符。每个字符表示一块瓷砖的颜色,规则如下:

1)‘.’:黑色的瓷砖;

2)‘#’:红色的瓷砖;

3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每组数据中唯一出现一次。

当在一行中读入的是两个零时,表示输入结束。

【输出】

对每组数据,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

【输入样例】

6 9 
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
0 0

【输出样例】

45

提交 统计信息 提交记录

 【算法思路】:

  1. 首先找到初始位置的坐标。
  2. 使用深度优先搜索(DFS)算法来计算从初始位置出发能到达的瓷砖数。
  3. 在深度优先搜索过程中,对于每个相邻的黑色瓷砖,进行递归调用,同时将当前瓷砖标记为已访问。
  4. 根据题目要求,每个相邻的瓷砖只能是上下左右四个方向之一,因此使用一个方向数组 dx[] 和 dy[] 来分别表示下一步在 x 轴和 y 轴的偏移量。
  5. 在递归调用之前,将当前瓷砖标记为已访问,即将其改变为红色。
  6. 在递归调用之后,将当前瓷砖标记为未访问,即将其改变为黑色。
  7. 统计访问过的黑色瓷砖数量。
  8. 搜索结束后,输出访问过的黑色瓷砖数量。

【算法流程】:

  1. 读入矩阵的宽度w和高度h。
  2. 如果w和h都为0,表示输入结束,退出循环。
  3. 初始化一个二维字符数组grid用于存储瓷砖的颜色。
  4. 初始化一个二维布尔数组visited用于标记瓷砖是否访问过。
  5. 在读入每个瓷砖的颜色时,如果颜色为'@',表示初始位置,记录初始位置的坐标。
  6. 调用dfs函数计算从初始位置出发能到达的瓷砖数,并输出结果。
  7. 重复步骤2-6,直到输入结束。

其中dfs函数使用深度优先搜索算法来计算从初始位置出发能到达的瓷砖数。对于每个相邻的黑色瓷砖,进行递归调用,并将当前瓷砖标记为已访问。搜索结束后返回访问过的黑色瓷砖数量。

【代码实现】:

#include <iostream>
#include <vector>

using namespace std;

int dfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
    if (x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size() || grid[x][y] == '#' || visited[x][y]) {
        return 0;
    }

    visited[x][y] = true;

    int dx[] = {0, 0, -1, 1};
    int dy[] = {-1, 1, 0, 0};
    int res = 1;

    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i];
        int ny = y + dy[i];
        res += dfs(grid, visited, nx, ny);
    }

    return res;
}

int main() {
    int w, h;
    
    while (cin >> w >> h) {
        if (w == 0 && h == 0) {
            break;
        }

        vector<vector<char>> grid(h, vector<char>(w));
        vector<vector<bool>> visited(h, vector<bool>(w, false));

        int startX, startY;

        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                cin >> grid[i][j];
                if (grid[i][j] == '@') {
                    startX = i;
                    startY = j;
                }
            }
        }

        int ans = dfs(grid, visited, startX, startY);
        cout << ans << endl;
    }

    return 0;
}

创作不易,点个👍,观个注吧。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值