【问题标题】:Backtracking solution for programming exercise (fitting pipes)编程练习的回溯解决方案(安装管道)
【发布时间】:2012-03-30 04:08:51
【问题描述】:

我正在审查本地编程竞赛中的一个编程问题。

您可以下载问题here (pdf)。它是荷兰语,但图片将有助于理解它。

您收到一个 m*m 网格作为输入,其中包含一些管道和一些缺失点(问号)。 其余的管道必须放置在网格中,以便它们与其他管道连接。

每个管道都表示为一个字母(参见第 2 页的图片)。字母“A”的值为 1,“B”的值为 2,..

我尝试通过回溯来解决它(它还不能很好地工作)。但是由于网格可以是 10x10,这将太慢。 有人可以提出更好(更快)的解决方案/方法吗?

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;

#define sz(a) int((a).size())
#define pb push_back

int m, found;
string letters;
vector<int> done;
vector<string> a;

int ok(char letter, int c, int r)
{
    int val = letter - 'A' + 1;

    //checking if no side goes outside
    if (r == 0 && (val & 1))
        return 0;
    if (r == m - 1 && (val & 4))
        return 0;
    if (c == 0 && (val & 8))
        return 0;
    if (c == m - 1 && (val & 2))
        return 0;

    //check if the side is connected the other pipe on the grid
    if (r > 0 && a[r - 1][c] != '?' && (a[r - 1][c] & 4) && !(val & 1))
        return 0;
    if (c > 0 && a[r][c - 1] != '?' && (a[r][c - 1] & 2) && !(val & 8))
        return 0;
    if (r < m - 1 && a[r + 1][c] != '?' && (a[r + 1][c] & 1) && !(val & 4))
        return 0;
    if (c < m - 1 && a[r][c + 1] != '?' && (a[r][c + 1] & 8) && !(val & 2))
        return 0;

    return 1;
}

void solve(int num_placed, int pos)
{
    if (found) return;

    //done
    if (num_placed == sz(letters)) {
        for (int i = 0; i < m; ++i)
            cout << a[i] << endl;
        found = 1;
        return;
    }

    int c = pos % m;
    int r = pos / m;
    if (a[r][c] != '?')
        solve(num_placed, pos + 1);

    //try all the pipes on this position
    for (int i = 0; i < sz(letters); ++i) {
        if (!done[i] && ok(letters[i], c, r)) {
            a[r][c] = letters[i];
            done[i] = 1;
            solve(num_placed + 1, pos + 1);
            done[i] = 0;
            a[r][c] = '?';
        }
    }
}

int main()
{
    freopen("input.txt", "r", stdin);

    int n;
    cin >> n;

    while (n--) {
        cin >> m;
        cin >> letters;

        cout << m << endl;
        a.clear();
        for (int i = 0; i < m; ++i) {
            string line;
            cin >> line;
            a.pb(line);
        }

        done = vector<int>(sz(letters), 0);

        found = 0;
        solve(0, 0);
    }

    return 0;
}

【问题讨论】:

标签: c++ algorithm recursion backtracking


【解决方案1】:

原始回复

您必须自己编写所有代码还是有兴趣探索其他工具?因为我建议查看约束传播/线性规划。你已经有很多边界约束——外边缘不能有管道,加上内边缘——所以我想这会非常有效。此外,约束看起来像是简单的等式,所以应该很容易设置。

我没有足够的经验在这里提供更多细节(尽管如果我下周有时间我可能会在某个时候试一试),但如果这种事情很有趣,还有更多背景在another answer i wrote some time ago

ps 有趣的问题;感谢您发布此内容。

[编辑:如果您不能使用其他库,那么您可以自己进行约束传播。有一个很棒的article by norvig 展示了如何为数独做到这一点。我强烈建议您阅读 - 我想您会看到如何使用这些技术,即使它是数独和 python。]

更新回复(2012-04-06 - 更新了博客参考;旧的 cmets 有问题)

深度优先搜索,其中下一个空单元格被每个可用的一致图块填充,并且一致性检查包括边缘约束(没有管道离开边缘)和最近的邻居,是相当有效率。我在 clojure 中有一个未优化的实现,它将在 0.4 毫秒左右(JVM 预热后 360 毫秒内 1000 个)和 3 毫秒内解决较大的示例(cedric van goethem 报告优化了 1 毫秒 - 但仍然是 OO - java 实现,这似乎是合理的)。它可以在 12 秒内解决一个 10x10 的难题(没有初始提示的同心圆管)。

我还花时间研究了一种“智能”方法,该方法可以跟踪每个单元格的约束,就像上面 norvig 的论文一样。然后我尝试使用choco。所有这一切都在blog posts here 中有更详细的描述(我在这个答案的更新中确实有更多的细节,但它们是基于错误的代码——博客有更多、更好的信息)。源代码也可供下载。

所有这些的一般结论是直接搜索可以达到 10x10。在那之后,更多的聪明可能会有所帮助,但很难确定,因为生成测试用例很困难(它们往往是模棱两可的,即使给出了很多单元格)。

【讨论】:

  • 是的,它属于那个问题类别,但我不能使用非标准库
  • norvig 写了一篇关于解决数独的非常好的文章,展示了如何在不使用专用包的情况下将回溯搜索与其他约束传播相结合。目前您已经进行了搜索,但没有使用其他约束(例如管道不跨越边界)。所以我建议阅读norvig.com/sudoku.html,他在其中详细描述了如何解决此类问题。
  • 谢谢安德鲁,写得很好。它可能会有所帮助
【解决方案2】:

很好的问题。我在O(n·m·8^m)中找到了一个解决方案,似乎已经足够了。

  1. 关注第一行和第二行之间的边界。有 2^m 种可能性(每一侧是否有传出线)。这将是上边界线,下边界线将在每一侧没有连接。

  2. 对于每对下边界线和上边界线(将是 2^m·2^m = 4^m 对),计算适合的每一行。如果你从左边来,你给出了左侧、顶部和底部,所以你只有 2 种可能性。如果您查看的图块在地图中是固定的,请检查它是否适合并中止。递归调用它,你会得到每行 2^m 行或总共 4^m*2^m=8^m。

  3. 虽然最后一步是纯蛮力,但这次我们使用 DP。保护数组数组中的元组(边框、使用的砖块、行)。 array[0] 将包含一个元组(空边框,不使用砖块,什么都没有)。 array[n] 包含第 n 行(从 1 开始)中所有 8^m 生成的行,以及它适合的 array[n-1] 中的每个项目(即项目的边界与行的下边界相同) 请注意,如果您巧妙地将这个条件与第 2 步中的循环结合起来,则无需任何成本。

  4. 删除所有需要一种类型的砖比可用的砖多的所有元组,并对数组进行排序。然后继续第 2 步,直到处理完所有行。

  5. 如果您已完成,请在数组[n] 中检查元组(空边框、所有砖块使用、行)。由于您的任务描述暗示它存在,因此打印出它的行。然后查看行的下边界,在array[n-1]中搜索并打印,以此类推。

希望你能听懂我的英语。

【讨论】:

  • 感谢您的回复,但恐怕我不明白您的解决方案。
  • 由 DP 他指的是动态规划。
猜你喜欢
  • 2021-04-15
  • 2013-11-12
  • 2017-12-20
  • 2016-01-23
  • 1970-01-01
  • 2017-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多