用于编程练习(管件)的回溯解决方案
Backtracking solution for programming exercise (fitting pipes)
我正在复习一个本地编程竞赛中的编程问题。
你可以在这里下载这个问题(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)中找到了一个解,它似乎是足够的。
-
关注第一行和第二行之间的边界。有2^m的可能性(出线与否,每侧)。这将是上边界线,下边界线的每一侧都没有连接。
-
对于每对下边界线和上边界线(将是2^m·2^m=4^m对),计算适合的每一行。如果你来自左边,你会得到左边、上边和下边,所以你只有两种可能性。如果您查看的平铺在地图中是固定的,请检查它是否适合,否则将中止。递归调用它,你会得到2^m行,或者总共4^m*2^m=8^m。
-
虽然最后一步是纯暴力,但这次我们使用DP。在数组数组中保护元组(border、bricks used、row)。数组[0]将包含一个元组(边界为空,未使用砖块,什么都没有)。array[n]包含第n行(从1开始)中生成的所有8^m行,以及数组[n-1]中适合它的每个项(即,项的边界与行的下边界相同)。请注意,如果您在步骤2中巧妙地将此条件与循环相结合,则不会产生任何成本。
-
删除所有需要一个排序的砖块多于可用砖块的元组,并对数组进行排序。然后继续执行步骤2,直到处理完所有行。
-
如果已经完成,请检入元组的array[n](空边界、使用的所有砖块、行)。由于任务描述暗示它存在,请打印出它的行。然后查看行的下边界,在数组[n-1]中搜索并打印它,依此类推
希望你能听懂我的英语。
- 运行同一解决方案的另一个项目的项目
- Project Euler问题4的错误解决方案
- 计算每个节点的树高,帮助我解释这个代码解决方案
- C++:Application.cpp中抛出了未解析的外部符号(解决方案在问题的末尾,供未来的读者参考)
- visual c++,如何获取解决方案目录中的代码
- 有没有办法在远程设备上打开和编辑visual Studio 2017解决方案
- C++Matching Brackets 2解决方案不起作用
- 在 ubuntu3 上C++ goto 定义有什么解决方案吗16.04?
- 在 leetcode 上提交解决方案时出现堆栈缓冲区溢出错误
- 我的固定时间步长与增量时间和插值的解决方案是错误的吗?
- 无法在问题解决方案中执行输出逻辑
- 最大的回文产品 - 程序未运行,编写解决方案但无法理解问题
- 从预序遍历构造 bst 的 c++ 和 python 解决方案之间的区别
- 在一个解决方案中针对第三方静态库 (Creo) 的不同版本(版本)进行构建
- 如何巧妙地编写两个函数——一个用于检查是否存在解决方案,另一个用于获取所有解决方案
- 为什么回溯解决方案给出错误的答案
- n-Rooks的解决方案数量回溯
- 有没有解决方案而不是在C++中移动背包回溯算法
- 用于编程练习(管件)的回溯解决方案
- 与回溯挂钩纸牌解决方案