Bootstrap

迭代加深——AcWing 170. 加成序列

迭代加深

定义

迭代加深搜索(Iterative Deepening Depth-First Search, IDS)是一种结合了深度优先搜索(DFS)和广度优先搜索(BFS)特点的算法。它通过限制搜索树的深度来控制搜索范围,起初以较小的深度限制进行搜索,如果没有找到解,则逐渐增加深度限制,重复搜索过程,直到找到解为止。这种方法既保留了DFS的空间效率(因为它不需要像BFS那样一次性生成所有深度级别的节点),又具有BFS的“全面性”(最终能找到解,如果存在的话),同时还能有效避免陷入深度过大的搜索分支。

运用情况

迭代加深搜索特别适用于那些有深度限制或者不知道最优解深度的问题,如棋盘游戏(如骑士巡逻问题)、迷宫探索、最短路径查找(特别是在有界情况下)等。它在处理存在解但深度未知,且深度较深的情况下,比普通DFS更有效率,因为后者可能会陷入深度过大的分支而无法及时找到解。

注意事项

  1. 深度限制:每次迭代时都需要设定一个深度限制,随着迭代次数增加,深度限制也随之增加。
  2. 剪枝:在搜索过程中要有效利用剪枝技术,比如基于限界函数的剪枝,避免无效的深度探索。
  3. 记忆化:虽然不是必须的,但使用记忆化技术(记录已访问状态)可以显著减少重复计算,提升效率。
  4. 资源管理:由于深度逐步增加,需要注意控制内存使用,避免栈溢出等问题。

解题思路

  1. 初始化:设置初始深度限制d=1,记录已访问状态(如果有记忆化需求)。
  2. 循环迭代:在一个循环中,执行以下步骤直到找到解:
    • 深度受限DFS:使用深度限制d进行深度优先搜索。在搜索过程中,如果到达叶子节点且满足目标条件,则找到了解,结束搜索。
    • 未找到解:如果当前深度的搜索没有找到解,则增加深度限制d=d+1,继续下一轮迭代。
  3. 退出条件:当达到某个预设的最大深度或者找到解时,结束迭代。
  4. 优化:在搜索过程中,利用剪枝策略减少不必要的探索,如基于深度的剪枝、基于代价的剪枝等。

迭代加深搜索的核心优势在于它能够逐步扩大搜索范围,同时保持了良好的空间效率和逐步接近最优解的能力,非常适合于那些深度未知但有界的问题场景。

AcWing 170. 加成序列

题目描述

170. 加成序列 - AcWing题库

运行代码

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int n;
int path[N];

bool dfs(int u, int k)
{
    if (u == k) return path[u - 1] == n;

    bool st[N] = {0};
    for (int i = u - 1; i >= 0; i -- )
        for (int j = i; j >= 0; j -- )
        {
            int s = path[i] + path[j];
            if (s > n || s <= path[u - 1] || st[s]) continue;
            st[s] = true;
            path[u] = s;
            if (dfs(u + 1, k)) return true;
        }

    return false;
}

int main()
{
    path[0] = 1;
    while (cin >> n, n)
    {
        int k = 1;
        while (!dfs(1, k)) k ++ ;

        for (int i = 0; i < k; i ++ ) cout << path[i] << ' ';
        cout << endl;
    }

    return 0;
}

代码思路

  1. 初始化与输入:定义了一个全局变量path[N]来存储当前搜索到的序列,初始化path[0]=1,然后不断读入整数n,直到输入为0停止。
  2. 深度优先搜索:定义了dfs(u, k)函数,其中u代表当前正在尝试填充序列的位置,k是序列期望的最大长度。该函数试图从位置u开始构建序列,如果成功构建到长度为k且最后一个元素为n的序列,则返回true
    • 对于每个位置u,遍历所有可能的前两个元素组合(i, j),计算它们的和s,并检查s是否适合作为path[u](即不大于n,不在序列中出现过,且大于path[u-1])。
    • 如果找到合适的s,则标记s为已使用,并继续递归搜索下一个位置。
  3. 主循环:从长度为2的序列开始尝试,不断增加序列长度k,直到找到满足条件的序列为止。

改进思路

  1. 剪枝优化:原代码中每次搜索都会重新初始化st[N]数组,这是一个不必要的开销。可以在外部定义并重用该数组,仅在每次搜索开始前清零。
  2. 减少重复计算:在搜索过程中,可以通过记录并检查之前已经尝试过的和来避免重复计算,进一步优化搜索效率。
  3. 优化搜索起点:考虑到序列的构造特性,搜索时可以从上次成功的位置继续,而不是每次都从头开始。

改进代码

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int n, k;
int path[N];
bool st[N];

bool dfs(int u)
{
    if (u == k) return path[u - 1] == n;

    for (int i = u - 1; i >= 0; i--) 
        for (int j = i; j >= 0; j--) 
        {
            int s = path[i] + path[j];
            if (s > n || s <= path[u - 1] || st[s]) continue;
            st[s] = true;
            path[u] = s;
            if (dfs(u + 1)) return true;
            st[s] = false; // 回溯
        }

    return false;
}

int main()
{
    while (cin >> n, n)
    {
        k = 1;
        memset(st, false, sizeof st);
        path[0] = 1;
        
        while (!dfs(1)) k++; // 直接在循环中增加k,直到找到解

        for (int i = 0; i < k; i++) cout << path[i] << ' ';
        cout << endl;
    }

    return 0;
}
;