Problem:
MathJax.Hub.Config({ tex2jax: { inlineMath: [["$","$"], ["\\(","\\)"]], processEscapes: true }});

15 Puzzle

The goal of the 15 puzzle problem is to complete pieces on $4 \times 4$ cells where one of the cells is empty space.

In this problem, the space is represented by 0 and pieces are represented by integers from 1 to 15 as shown below.

1 2 3 4
6 7 8 0
5 10 11 12
9 13 14 15

You can move a piece toward the empty space at one step. Your goal is to make the pieces the following configuration in the shortest move (fewest steps).

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 0

Write a program which reads an initial state of the puzzle and prints the fewest steps to solve the puzzle.

Input

The $4 \times 4$ integers denoting the pieces or space are given.

Output

Print the fewest steps in a line.

Constraints

The given puzzle is solvable in at most 45 steps.

Sample Input

1 2 3 4
6 7 8 0
5 10 11 12
9 13 14 15

Sample Output

8

Solution in C# (judge verdict: Time Limit Exceeded)

using System;
using System.Linq;
using System.Collections.Generic;
public static class Extension
{
	public static void ForEach<T>(this IEnumerable<T> e, Action<T, int> action)
	{
		var i = 0;
		foreach (var element in e) action(element, i++);
	}

}
//using System;
//using System.Linq;
//using System.Collections.Generic;
//public static class Extension
//{
//	public static void ForEach<T>(this IEnumerable<T> e, Action<T, int> action)
//	{
//		var i = 0;
//		foreach (var element in e) action(element, i++);
//	}
//
//}


public class FifteenPuzzle
{
    public static void Main()
    {
        var inputs = Console.ReadLine().Split(' ').Select(int.Parse)
            .Concat(Console.ReadLine().Split(' ').Select(int.Parse))
            .Concat(Console.ReadLine().Split(' ').Select(int.Parse))
            .Concat(Console.ReadLine().Split(' ').Select(int.Parse))
            .Cast<int>()
            .ToArray();
        
        foreach (var max in Enumerable.Range(1, 45))
        {
            if (!Board.Move(new Board(inputs), 0, max))
                continue;
            Console.WriteLine(max);
            break;
        }
    }
}

public class Board
{
    public Node[] Nodes { get; set; }
    public HashSet<long> Hash { get; private set; }
    public Board(IEnumerable<int> inputs)
    {
        Nodes = inputs.Select((x, i) => new Node((i + 1) % 16, x))
            .ToArray();
        Hash = new HashSet<long>();
    }
    
    public Board() { }
    
//    public Board[] Move(int count, int max)
//    {
//        var empty = Nodes.First(x => x.Current == 0);
//        var children = empty.AdjacentNumbers
//            .Select(_ => Board.Clone(this))
//            .ToArray();
//        
//        empty.AdjacentNumbers
//            .ForEach((adj, i) =>
//            {
//                var board = children[i];
//                var left = board.Nodes.First(x => x.Current == 0);
//                var right = board.Nodes.First(x => x.CorrectNumber == adj);
//                
//                board.SwapCurrent(left, right);
//                
//                if (!hash.Add(board.GetHash()))
//                    children[i] = null;
//                else if (board.Nodes.Where(x => x.Current != 0).Sum(x => GetManhattanDistance(x.Current, x.CorrectNumber)) > max - count)
//                    children[i] = null;
//            });
//        
//        return children.Where(x => x != null).ToArray();
//    }
    public static bool Move(Board board, int count, int max)
    {
        if (board.Nodes.All(x => x.IsCorrect()))
            return true;
        var empty = board.Nodes.First(x => x.Current == 0);
        var children = empty.AdjacentNumbers
            .Select(_ => Board.Clone(board))
            .ToArray();
        
        empty.AdjacentNumbers
            .ForEach((adj, i) =>
            {
                var child = children[i];
                var left = child.Nodes.First(x => x.Current == 0);
                var right = child.Nodes.First(x => x.CorrectNumber == adj);
                
                child.SwapCurrent(left, right);
                
                if (!child.Hash.Add(child.GetHash()))
                    children[i] = null;
                else
                {
                    var distance = child.Nodes.Where(x => x.Current != 0).Sum(x => GetManhattanDistance(x.Current, x.CorrectNumber));
                    if (distance >= max - count)
                    {
                        children[i] = null;
                    }
                }
            });
        children = children.Where(x => x != null).ToArray();
        
//        if (children.Any(x => x.Nodes.All(node => node.IsCorrect()))) return true;
        
        return children.Any(x => Board.Move(x, count + 1, max));
    }

    public void SwapCurrent(Node left, Node right)
    {
        var temp = left.Current;
        left.Current = right.Current;
        right.Current = temp;
    }
    
    private static Board Clone(Board board)
    {
        return new Board
        {
            Nodes = board.Nodes.Select(Node.Clone)
                .ToArray(),
            Hash = new HashSet<long>(board.Hash)
        };
    }
    
    public override string ToString()
    {
        return Nodes.Select(x => x.Current)
            .Select(x => x.ToString())
            .Aggregate((acc, x) => acc + " " + x);
    }

    public long GetHash()
    {
        var code = Nodes.Select(x => x.Current).Aggregate((accum, x) => accum * 10 + x);
        return code;
    }
    
    private static int GetManhattanDistance(int current, int correct)
    {
        var cu = current - 1;
        var co = correct == 0 ? 15 : correct - 1;
        var d = Math.Abs(cu / 4 - co / 4) + Math.Abs(cu % 4 - co % 4);
        return d;
    }
}

public class Node
{
    private static readonly Dictionary<int, int[]> adjacentNumbersDic =
        new Dictionary<int, int[]>
        {
            { 1, new [] { 2, 5 } },
            { 2, new [] { 1, 3, 6 } },
            { 3, new [] { 2, 4, 7 } },
            { 4, new [] { 3, 8 } },
            { 5, new [] { 1, 6, 9 } },
            { 6, new [] { 2, 5, 7, 10 } },
            { 7, new [] { 3, 6, 8, 11 } },
            { 8, new [] { 4, 7, 12 } },
            { 9, new [] { 5, 10, 13 } },
            { 10, new [] { 6, 9, 11, 14 } },
            { 11, new [] { 7, 10, 12, 15 } },
            { 12, new [] { 8, 11, 0 } },
            { 13, new [] { 9, 14 } },
            { 14, new [] { 10, 13, 15 } },
            { 15, new [] { 11, 14, 0 } },
            { 0, new [] { 12, 15 } },
        };

    public int CorrectNumber { get; private set; }    
    public int Current { get; set; }
    public int[] AdjacentNumbers { get; set; }

    public Node(int correct, int current)
    {
        CorrectNumber = correct;
        Current = current;
        AdjacentNumbers = adjacentNumbersDic[correct];
    }
    
    public static Node Clone(Node node)
    {
        return new Node(node.CorrectNumber, node.Current);
    }
    
    public bool IsCorrect() { return CorrectNumber == Current; }
}