用于编程练习(管件)的回溯解决方案

Backtracking solution for programming exercise (fitting pipes)

本文关键字:回溯 解决方案 管件 编程 练习 用于      更新时间:2023-10-16

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

你可以在这里下载这个问题(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;
}

原始回复

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

我没有足够的经验在这里提供更多的细节(尽管如果我下周有时间,我可能会在某个时候尝试一下),但如果这种事情很有趣,我不久前写的另一个答案中有更多的背景。

ps有趣的问题;谢谢你发布这篇文章。

[编辑:如果你不能使用其他库,那么你可以自己进行约束传播。norvig有一篇精彩的文章展示了如何为数独做这件事。我强烈建议你阅读这篇文章-我想你会看到如何将这些技术传播开来,即使它是数独和python。]

更新回复(2012-04-06-更新博客参考资料;旧评论有缺陷)

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

我还花时间研究了一种"智能"方法,它跟踪每个单元格上的约束,很像norvig上面的论文。然后我试着用巧克力。所有这些在这里的博客文章中都有更详细的描述(我在这个答案的更新中确实有更多的细节,但它们是基于错误代码的——博客有更多更好的信息)。该源代码也可供下载。

由此得出的一般结论是,直接搜索可以达到10x10。之后,更多的智能可能会有所帮助,但很难确定,因为生成测试用例很难(即使给出了很多单元格,它们也往往是模糊的)。

问题不错。我在O(n·m·8^m)中找到了一个解,它似乎是足够的。

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

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

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

  4. 删除所有需要一个排序的砖块多于可用砖块的元组,并对数组进行排序。然后继续执行步骤2,直到处理完所有行。

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

希望你能听懂我的英语。