在水彩画游戏中,游戏的目标是使整个棋盘在尽可能少的转弯中保持相同的颜色。
游戏的开头是一块类似于这样的棋盘:
3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 5 5 5 4 1 4
6 2 5 3[3]1 1 6 6
5 5 1 2 5 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4目前,板中心的数字(代表一种颜色)为3。每转一圈,中心的正方形就会改变颜色,所有从中心移动到同一颜色的方格(即在中心广场的洪流区)都会随着颜色的变化而改变颜色。因此,如果正方形改变颜色为5:
3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 5 5 5 4 1 4
6 2 5 5[5]1 1 6 6
5 5 1 2 5 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4然后,中间3左边的3也会改变颜色。现在总共有7名5英尺的S可以从中间到达,所以如果我们把颜色改为4:
3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 4 4 4 4 1 4
6 2 4 4[4]1 1 6 6
5 5 1 2 4 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4油漆区域的面积再次急剧增加。
您的任务是创建一个程序,该程序将以从1到6的19×19颜色网格作为输入,无论您选择何种形式:
4 5 1 1 2 2 1 6 2 6 3 4 2 3 2 3 1 6 3
4 2 6 3 4 4 5 6 4 4 5 3 3 3 3 5 4 3 4
2 3 5 2 2 5 5 1 2 6 2 6 6 2 1 6 6 1 2
4 6 5 5 5 5 4 1 6 6 3 2 6 4 2 6 3 6 6
1 6 4 4 4 4 6 4 2 5 5 3 2 2 4 1 5 2 5
1 6 2 1 5 1 6 4 4 1 5 1 3 4 5 2 3 4 1
3 3 5 3 2 2 2 4 2 1 6 6 6 6 1 4 5 2 5
1 6 1 3 2 4 1 3 3 4 6 5 1 5 5 3 4 3 3
4 4 1 5 5 1 4 6 3 3 4 5 5 6 1 6 2 6 4
1 4 2 5 6 5 5 3 2 5 5 5 3 6 1 4 4 6 6
4 6 6 2 6 6 2 4 2 6 1 5 6 2 3 3 4 3 6
6 1 3 6 3 5 5 3 6 1 3 4 4 5 1 2 6 4 3
2 6 1 3 2 4 2 6 1 1 5 2 6 6 6 6 3 3 3
3 4 5 4 6 6 3 3 4 1 1 6 4 5 1 3 4 1 2
4 2 6 4 1 5 3 6 4 3 4 5 4 2 1 1 4 1 1
4 2 4 1 5 2 2 3 6 6 6 5 2 5 4 5 4 5 1
5 6 2 3 4 6 5 4 1 3 2 3 2 1 3 6 2 2 4
6 5 4 1 3 2 2 1 1 1 6 1 2 6 2 5 6 4 5
5 1 1 4 2 6 2 5 6 1 3 3 4 1 6 1 2 1 2并返回一个颜色序列,中心广场将改变到每一个回合,再次以您选择的格式:
263142421236425431645152623645465646213545631465在每个移动序列的末尾,19乘19网格中的方格必须都是相同的颜色。
您的程序必须是完全确定性的;允许伪随机解决方案,但程序每次必须为相同的测试用例生成相同的输出。
获胜的程序将采取最少的步骤来解决在这个文件中找到的所有100,000个测试用例(压缩文本文件,14.23MB)。如果两个解决方案采取相同的步骤(例如,如果它们都找到了最优策略),那么较短的程序将获胜。
BurntPizza用Java编写了一个程序来验证测试结果。若要使用此程序,请运行提交文件并将输出输送到一个名为steps.txt的文件中。然后,使用steps.txt和floodtest文件在同一个目录下运行这个程序。如果条目有效,并为所有文件生成正确的解决方案,则应通过所有测试并返回All boards solved successfully.。
import java.io.*;
import java.util.*;
public class PainterVerifier {
public static void main(String[] args) throws FileNotFoundException {
char[] board = new char[361];
Scanner s = new Scanner(new File("steps.txt"));
Scanner b = new Scanner(new File("floodtest"));
int lineNum = 0;
caseloop: while (b.hasNextLine()) {
for (int l = 0; l < 19; l++) {
String lineb = b.nextLine();
if (lineb.isEmpty())
continue caseloop;
System.arraycopy(lineb.toCharArray(), 0, board, l * 19, 19);
}
String line = s.nextLine();
if (line.isEmpty())
continue;
char[] steps = line.toCharArray();
Stack<Integer> nodes = new Stack<Integer>();
for (char c : steps) {
char targetColor = board[180];
char replacementColor = c;
nodes.push(180);
while (!nodes.empty()) {
int n = nodes.pop();
if (n < 0 || n > 360)
continue;
if (board[n] == targetColor) {
board[n] = replacementColor;
if (n % 19 > 0)
nodes.push(n - 1);
if (n % 19 < 18)
nodes.push(n + 1);
if (n / 19 > 0)
nodes.push(n - 19);
if (n / 19 < 18)
nodes.push(n + 19);
}
}
}
char center = board[180];
for (char c : board)
if (c != center) {
s.close();
b.close();
System.out.println("\nIncomplete board found!\n\tOn line " + lineNum + " of steps.txt");
System.exit(0);
}
if (lineNum % 5000 == 0)
System.out.printf("Verification %d%c complete...\n", lineNum * 100 / 100000, '%');
lineNum++;
}
s.close();
b.close();
System.out.println("All boards solved successfully.");
}
}另外,一个记分板,因为结果实际上不是按分数排序的,在这里,它实际上很重要:
发布于 2018-03-15 21:54:24
https://github.com/smack42/ColorFill
又来晚了一次。包含1,985,078个步骤的结果文件可以找到这里。
几年前,我发现了这个挑战,当时我开始编写自己的克隆版游戏Flood-it。
“最佳不完全”DFS与A*算法
从一开始,我就想为这个游戏创建一个很好的算法。随着时间的推移,我可以通过包括几种不同的不完全搜索策略(类似于这里的其他程序中使用的策略)和对每个解决方案使用这些策略的最佳结果来改进我的解决方案。我甚至用Java重新实现了蒂格鲁氏A*算法,并将它添加到我的解决程序中,以获得比tigrou的结果更好的整体解决方案。
穷举DFS算法
然后,我重点讨论了一种总是能找到最优解的算法。我花了很多精力来优化我的深度优先搜索策略。为了加快搜索速度,我包括了一个hashmap,它存储所有已探测到的状态,这样搜索就可以避免再次搜索它们。虽然这个算法工作得很好,并且足够快地解决了所有14x14个谜题,但是它占用了太多的内存,并且在这个代码挑战中的19x19谜题中运行非常慢。
Puchert A*算法
几个月前,有人联系我看洪水解决者亚伦和西蒙普切特。该程序使用A*型算法和允许的启发式(相对于tigrou)和移动剪枝类似于跳点搜索。我很快注意到这个程序非常快,并找到了最优的解决方案!
当然,我不得不重新实现这个伟大的算法,并将其添加到我的程序中。我努力优化我的Java程序,使其运行速度与Puchert兄弟最初的C++程序一样快。然后我决定尝试这个挑战的100,000个测试用例。在我的机器上,程序运行了超过120个小时,使用我实现的Puchert A*算法找到了1,985,078个步骤。
这将是这个挑战的最佳解决方案,除非程序中有一些错误会导致次优解决方案。欢迎任何反馈!
编辑:在这里添加了代码的相关部分:
类AStarPuchertStrategy
/**
* a specific strategy for the AStar (A*) solver.
* <p>
* the idea is taken from the program "floodit" by Aaron and Simon Puchert,
* which can be found at <a>https://github.com/aaronpuchert/floodit</a>
*/
public class AStarPuchertStrategy implements AStarStrategy {
private final Board board;
private final ColorAreaSet visited;
private ColorAreaSet current, next;
private final short[] numCaNotFilledInitial;
private final short[] numCaNotFilled;
public AStarPuchertStrategy(final Board board) {
this.board = board;
this.visited = new ColorAreaSet(board);
this.current = new ColorAreaSet(board);
this.next = new ColorAreaSet(board);
this.numCaNotFilledInitial = new short[board.getNumColors()];
for (final ColorArea ca : board.getColorAreasArray()) {
++this.numCaNotFilledInitial[ca.getColor()];
}
this.numCaNotFilled = new short[board.getNumColors()];
}
/* (non-Javadoc)
* @see colorfill.solver.AStarStrategy#setEstimatedCost(colorfill.solver.AStarNode)
*/
@Override
public void setEstimatedCost(final AStarNode node) {
// quote from floodit.cpp: int State::computeValuation()
// (in branch "performance")
//
// We compute an admissible heuristic recursively: If there are no nodes
// left, return 0. Furthermore, if a color can be eliminated in one move
// from the current position, that move is an optimal move and we can
// simply use it. Otherwise, all moves fill a subset of the neighbors of
// the filled nodes. Thus, filling that layer gets us at least one step
// closer to the end.
node.copyFloodedTo(this.visited);
System.arraycopy(this.numCaNotFilledInitial, 0, this.numCaNotFilled, 0, this.numCaNotFilledInitial.length);
{
final ColorAreaSet.FastIteratorColorAreaId iter = this.visited.fastIteratorColorAreaId();
int nextId;
while ((nextId = iter.nextOrNegative()) >= 0) {
--this.numCaNotFilled[this.board.getColor4Id(nextId)];
}
}
// visit the first layer of neighbors, which is never empty, i.e. the puzzle is not solved yet
node.copyNeighborsTo(this.current);
this.visited.addAll(this.current);
int completedColors = 0;
{
final ColorAreaSet.FastIteratorColorAreaId iter = this.current.fastIteratorColorAreaId();
int nextId;
while ((nextId = iter.nextOrNegative()) >= 0) {
final byte nextColor = this.board.getColor4Id(nextId);
if (--this.numCaNotFilled[nextColor] == 0) {
completedColors |= 1 << nextColor;
}
}
}
int distance = 1;
while(!this.current.isEmpty()) {
this.next.clear();
final ColorAreaSet.FastIteratorColorAreaId iter = this.current.fastIteratorColorAreaId();
int thisCaId;
if (0 != completedColors) {
// We can eliminate colors. Do just that.
// We also combine all these elimination moves.
distance += Integer.bitCount(completedColors);
final int prevCompletedColors = completedColors;
completedColors = 0;
while ((thisCaId = iter.nextOrNegative()) >= 0) {
final ColorArea thisCa = this.board.getColorArea4Id(thisCaId);
if ((prevCompletedColors & (1 << thisCa.getColor())) != 0) {
// completed color
// expandNode()
for (final int nextCaId : thisCa.getNeighborsIdArray()) {
if (!this.visited.contains(nextCaId)) {
this.visited.add(nextCaId);
this.next.add(nextCaId);
final byte nextColor = this.board.getColor4Id(nextCaId);
if (--this.numCaNotFilled[nextColor] == 0) {
completedColors |= 1 << nextColor;
}
}
}
} else {
// non-completed color
// move node to next layer
this.next.add(thisCaId);
}
}
} else {
// Nothing found, do the color-blind pseudo-move
// Expand current layer of nodes.
++distance;
while ((thisCaId = iter.nextOrNegative()) >= 0) {
final ColorArea thisCa = this.board.getColorArea4Id(thisCaId);
// expandNode()
for (final int nextCaId : thisCa.getNeighborsIdArray()) {
if (!this.visited.contains(nextCaId)) {
this.visited.add(nextCaId);
this.next.add(nextCaId);
final byte nextColor = this.board.getColor4Id(nextCaId);
if (--this.numCaNotFilled[nextColor] == 0) {
completedColors |= 1 << nextColor;
}
}
}
}
}
// Move the next layer into the current.
final ColorAreaSet tmp = this.current;
this.current = this.next;
this.next = tmp;
}
node.setEstimatedCost(node.getSolutionSize() + distance);
}
}AStarSolver类的一部分
private void executeInternalPuchert(final ColorArea startCa) throws InterruptedException {
final Queue<AStarNode> open = new PriorityQueue<AStarNode>(AStarNode.strongerComparator());
open.offer(new AStarNode(this.board, startCa));
AStarNode recycleNode = null;
while (open.size() > 0) {
if (Thread.interrupted()) { throw new InterruptedException(); }
final AStarNode currentNode = open.poll();
if (currentNode.isSolved()) {
this.addSolution(currentNode.getSolution());
return;
} else {
// play all possible colors
int nextColors = currentNode.getNeighborColors(this.board);
while (0 != nextColors) {
final int l1b = nextColors & -nextColors; // Integer.lowestOneBit()
final int clz = Integer.numberOfLeadingZeros(l1b); // hopefully an intrinsic function using instruction BSR / LZCNT / CLZ
nextColors ^= l1b; // clear lowest one bit
final byte color = (byte)(31 - clz);
final AStarNode nextNode = currentNode.copyAndPlay(color, recycleNode, this.board);
if (null != nextNode) {
recycleNode = null;
this.strategy.setEstimatedCost(nextNode);
open.offer(nextNode);
}
}
}
recycleNode = currentNode;
}
}AStarNode类的一部分
/**
* check if this color can be played. (avoid duplicate moves)
* the idea is taken from the program "floodit" by Aaron and Simon Puchert,
* which can be found at <a>https://github.com/aaronpuchert/floodit</a>
* @param nextColor
* @return
*/
private boolean canPlay(final byte nextColor, final List<ColorArea> nextColorNeighbors) {
final byte currColor = this.solution[this.solutionSize];
// did the previous move add any new "nextColor" neighbors?
boolean newNext = false;
next: for (final ColorArea nextColorNeighbor : nextColorNeighbors) {
for (final ColorArea prevNeighbor : nextColorNeighbor.getNeighborsArray()) {
if ((prevNeighbor.getColor() != currColor) && this.flooded.contains(prevNeighbor)) {
continue next;
}
}
newNext = true;
break next;
}
if (!newNext) {
if (nextColor < currColor) {
return false;
} else {
// should nextColor have been played before currColor?
for (final ColorArea nextColorNeighbor : nextColorNeighbors) {
for (final ColorArea prevNeighbor : nextColorNeighbor.getNeighborsArray()) {
if ((prevNeighbor.getColor() == currColor) && !this.flooded.contains(prevNeighbor)) {
return false;
}
}
}
return true;
}
} else {
return true;
}
}
/**
* try to re-use the given node or create a new one
* and then play the given color in the result node.
* @param nextColor
* @param recycleNode
* @return
*/
public AStarNode copyAndPlay(final byte nextColor, final AStarNode recycleNode, final Board board) {
final List<ColorArea> nextColorNeighbors = new ArrayList<ColorArea>(128); // constant, arbitrary initial capacity
final ColorAreaSet.FastIteratorColorAreaId iter = this.neighbors.fastIteratorColorAreaId();
int nextId;
while ((nextId = iter.nextOrNegative()) >= 0) {
final ColorArea nextColorNeighbor = board.getColorArea4Id(nextId);
if (nextColorNeighbor.getColor() == nextColor) {
nextColorNeighbors.add(nextColorNeighbor);
}
}
if (!this.canPlay(nextColor, nextColorNeighbors)) {
return null;
} else {
final AStarNode result;
if (null == recycleNode) {
result = new AStarNode(this);
} else {
// copy - compare copy constructor
result = recycleNode;
result.flooded.copyFrom(this.flooded);
result.neighbors.copyFrom(this.neighbors);
System.arraycopy(this.solution, 0, result.solution, 0, this.solutionSize + 1);
result.solutionSize = this.solutionSize;
//result.estimatedCost = this.estimatedCost; // not necessary to copy
}
// play - compare method play()
for (final ColorArea nextColorNeighbor : nextColorNeighbors) {
result.flooded.add(nextColorNeighbor);
result.neighbors.addAll(nextColorNeighbor.getNeighborsIdArray());
}
result.neighbors.removeAll(result.flooded);
result.solution[++result.solutionSize] = nextColor;
return result;
}
}发布于 2014-05-10 23:03:01
我尝试了很多事情,大多数都失败了,只是直到最近才起作用。我有一件有趣的事可以贴出答案。
当然还有更多的方法来进一步改善这一点。我想走两百万步可能是可能的。
需要大约7 hours才能生成结果。下面是一个包含所有解决方案的txt文件,以防有人想要检查它们:results.zip
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace FloodPaintAI
{
class Node
{
public byte Value; //1-6
public int Index; //unique identifier, used for easily deepcopying the graph
public List<Node> Neighbours;
public List<Tuple<int, int>> NeighboursPositions; //used by BuildGraph()
public int Depth; //used by GetSumDistances()
public bool Checked; //
public Node(byte value, int index)
{
Value = value;
Index = index;
}
public Node(Node node)
{
Value = node.Value;
Index = node.Index;
}
}
class Board
{
private const int SIZE = 19;
private const int STARTPOSITION = 9;
public Node Root; //root of graph. each node is a set of contiguous, same color square
public List<Node> Nodes; //all nodes in the graph, used for deep copying
public int EstimatedCost; //estimated cost, used by A* Pathfinding
public List<byte> Solution;
public Board(StreamReader input)
{
byte[,] board = new byte[SIZE, SIZE];
for(int j = 0 ; j < SIZE ; j++)
{
string line = input.ReadLine();
for(int i = 0 ; i < SIZE ; i++)
{
board[j, i] = byte.Parse(line[i].ToString());
}
}
Solution = new List<byte>();
BuildGraph(board);
}
public Board(Board boardToCopy)
{
//copy the graph
Nodes = new List<Node>(boardToCopy.Nodes.Count);
foreach(Node nodeToCopy in boardToCopy.Nodes)
{
Node node = new Node(nodeToCopy);
Nodes.Add(node);
}
//copy "Neighbours" property
for(int i = 0 ; i < boardToCopy.Nodes.Count ; i++)
{
Node node = Nodes[i];
Node nodeToCopy = boardToCopy.Nodes[i];
node.Neighbours = new List<Node>(nodeToCopy.Neighbours.Count);
foreach(Node neighbour in nodeToCopy.Neighbours)
{
node.Neighbours.Add(Nodes[neighbour.Index]);
}
}
Root = Nodes[boardToCopy.Root.Index];
EstimatedCost = boardToCopy.EstimatedCost;
Solution = new List<byte>(boardToCopy.Solution);
}
private void BuildGraph(byte[,] board)
{
int[,] nodeIndexes = new int[SIZE, SIZE];
Nodes = new List<Node>();
//check how much sets we have (1st pass)
for(int j = 0 ; j < SIZE ; j++)
{
for(int i = 0 ; i < SIZE ; i++)
{
if(nodeIndexes[j, i] == 0) //not already visited
{
Node newNode = new Node(board[j, i], Nodes.Count);
newNode.NeighboursPositions = new List<Tuple<int, int>>();
Nodes.Add(newNode);
BuildGraphFloodFill(board, nodeIndexes, newNode, i, j, board[j, i]);
}
}
}
//set neighbours and root (2nd pass)
foreach(Node node in Nodes)
{
node.Neighbours = new List<Node>();
node.Neighbours.AddRange(node.NeighboursPositions.Select(x => nodeIndexes[x.Item2, x.Item1]).Distinct().Select(x => Nodes[x - 1]));
node.NeighboursPositions = null;
}
Root = Nodes[nodeIndexes[STARTPOSITION, STARTPOSITION] - 1];
}
private void BuildGraphFloodFill(byte[,] board, int[,] nodeIndexes, Node node, int startx, int starty, byte floodvalue)
{
Queue<Tuple<int, int>> queue = new Queue<Tuple<int, int>>();
queue.Enqueue(new Tuple<int, int>(startx, starty));
while(queue.Count > 0)
{
Tuple<int, int> position = queue.Dequeue();
int x = position.Item1;
int y = position.Item2;
if(x >= 0 && x < SIZE && y >= 0 && y < SIZE)
{
if(nodeIndexes[y, x] == 0 && board[y, x] == floodvalue)
{
nodeIndexes[y, x] = node.Index + 1;
queue.Enqueue(new Tuple<int, int>(x + 1, y));
queue.Enqueue(new Tuple<int, int>(x - 1, y));
queue.Enqueue(new Tuple<int, int>(x, y + 1));
queue.Enqueue(new Tuple<int, int>(x, y - 1));
}
if(board[y, x] != floodvalue)
node.NeighboursPositions.Add(position);
}
}
}
public int GetEstimatedCost()
{
Board current = this;
//copy current board and play the best color until the end.
//number of moves required to go the end is the heuristic
//estimated cost = current cost + heuristic
while(!current.IsSolved())
{
int minSumDistance = int.MaxValue;
Board minBoard = null;
//find color which give the minimum sum of distance from root to each other node
foreach(byte i in current.Root.Neighbours.Select(x => x.Value).Distinct())
{
Board copy = new Board(current);
copy.Play(i);
int distance = copy.GetSumDistances();
if(distance < minSumDistance)
{
minSumDistance = distance;
minBoard = copy;
}
}
current = minBoard;
}
return current.Solution.Count;
}
public int GetSumDistances()
{
//get sum of distances from root to each other node, using BFS
int sumDistances = 0;
//reset marker
foreach(Node n in Nodes)
{
n.Checked = false;
}
Queue<Node> queue = new Queue<Node>();
Root.Checked = true;
Root.Depth = 0;
queue.Enqueue(Root);
while(queue.Count > 0)
{
Node current = queue.Dequeue();
foreach(Node n in current.Neighbours)
{
if(!n.Checked)
{
n.Checked = true;
n.Depth = current.Depth + 1;
sumDistances += n.Depth;
queue.Enqueue(n);
}
}
}
return sumDistances;
}
public void Play(byte value)
{
//merge root node with other neighbours nodes, if color is matching
Root.Value = value;
List<Node> neighboursToRemove = Root.Neighbours.Where(x => x.Value == value).ToList();
List<Node> neighboursToAdd = neighboursToRemove.SelectMany(x => x.Neighbours).Except((new Node[] { Root }).Concat(Root.Neighbours)).ToList();
foreach(Node n in neighboursToRemove)
{
foreach(Node m in n.Neighbours)
{
m.Neighbours.Remove(n);
}
Nodes.Remove(n);
}
foreach(Node n in neighboursToAdd)
{
Root.Neighbours.Add(n);
n.Neighbours.Add(Root);
}
//re-synchronize node index
for(int i = 0 ; i < Nodes.Count ; i++)
{
Nodes[i].Index = i;
}
Solution.Add(value);
}
public bool IsSolved()
{
//return Nodes.Count == 1;
return Root.Neighbours.Count == 0;
}
}
class Program
{
public static List<byte> Solve(Board input)
{
//A* Pathfinding
LinkedList<Board> open = new LinkedList<Board>();
input.EstimatedCost = input.GetEstimatedCost();
open.AddLast(input);
while(open.Count > 0)
{
Board current = open.First.Value;
open.RemoveFirst();
if(current.IsSolved())
{
return current.Solution;
}
else
{
//play all neighbours nodes colors
foreach(byte i in current.Root.Neighbours.Select(x => x.Value).Distinct())
{
Board newBoard = new Board(current);
newBoard.Play(i);
newBoard.EstimatedCost = newBoard.GetEstimatedCost();
//insert board to open list
bool inserted = false;
for(LinkedListNode<Board> node = open.First ; node != null ; node = node.Next)
{
if(node.Value.EstimatedCost > newBoard.EstimatedCost)
{
open.AddBefore(node, newBoard);
inserted = true;
break;
}
}
if(!inserted)
open.AddLast(newBoard);
}
}
}
throw new Exception(); //no solution found, impossible
}
public static void Main(string[] args)
{
using (StreamReader sr = new StreamReader("floodtest"))
{
while(!sr.EndOfStream)
{
List<Board> boards = new List<Board>();
while(!sr.EndOfStream && boards.Count < 100)
{
Board board = new Board(sr);
sr.ReadLine(); //skip empty line
boards.Add(board);
}
List<byte>[] solutions = new List<byte>[boards.Count];
Parallel.For(0, boards.Count, i =>
{
solutions[i] = Solve(boards[i]);
});
foreach(List<byte> solution in solutions)
{
Console.WriteLine(string.Join(string.Empty, solution));
}
}
}
}
}
}关于其工作方式的更多详细信息:
它采用一个*寻路算法。
困难的是找到一个好的heuristic。如果heuristic低估了成本,它的性能就像迪克斯特拉算法一样,而且由于一个6种颜色的19x19板的复杂性,它将永远运行。如果它高估了成本,它将很快收敛到一个解决方案上,但根本不会给出好的解决方案(大约有26次移动,19次是可能的)。找到完美的heuristic,给出达到解决方案所需步骤的确切剩余量将是最好的(它将是快速的,并且会给出最佳的可能解决方案)。这是不可能的(除非我错了)。它实际上需要首先解决董事会本身,而这正是你真正想要做的(鸡和蛋问题)。
我尝试了很多东西,下面是heuristic的工作原理:
node表示一组相邻的、相同颜色的正方形。使用该graph,我可以很容易地计算出从中心到任何其他节点的精确最小距离。例如,从中间到左上角的距离是10,因为至少有10种颜色将它们分开。heuristic:我玩当前的板,直到结束。对于每一步,我选择颜色,这将最小化从根到所有其他节点的距离之和。heuristic。Estimated cost (由A*使用)= moves so far + heuristic它并不完美,因为它稍微高估了成本(因此找到了非最优解)。无论如何,它是快速的计算和提供良好的结果。
通过使用图形来执行所有的操作,我能够获得巨大的速度提升。开始时,我有一个2-dimension数组。当需要时,我给它注入洪水并重新计算图(例如:计算启发式)。现在所有的事情都是用图表来完成的,它只是在一开始计算出来的。为了模拟步骤,不再需要洪水填充,而是合并节点。这要快得多。
发布于 2014-04-23 23:13:35
作为最后的参考解决方案,请考虑以下顺序:
print "123456" * 18通过所有的颜色,n时间,意味着每一个平方n的步骤,将保证相同的颜色与中心广场。每个正方形最多离中心18步,所以18个周期将保证所有的方块都是相同的颜色。最有可能的情况下,它将在较少的完成,但程序不需要停止,只要所有的方块是相同的颜色,它只是更有益于这样做。这个不变的过程是每个测试用例108个步骤,总共10,800,000步。
https://codegolf.stackexchange.com/questions/26232
复制相似问题