使用Dancing link X (舞蹈链)求解dominosa游戏

  puzzle team club做了一个网站,里面有好多解谜游戏可以玩。身为程序员玩这种游戏,就应该想一些使用程序解决的方法。

  但是里面好多游戏还蛮难解的。。。我是说写出简明扼要的程序,并且像在ACM赛场一样执行和开发时间占优。所有游戏都需要用搜索完成毫无疑问。

  但是不少游戏都涉及到图论,单纯的搜索很难一下想到方法,像slither link(连接回路)、hashi(搭桥)、nirikabe(围城)和slithes(连接)这几个游戏就是跟图论相关。

  其他看起来不像图论的游戏,像skyscrapers(摩天大楼)、star battle(星星战斗),属于有限次数完全覆盖,很难一下想到简明的方法。

  这里只求解能使用舞蹈链解决的,相对简单的dominosa游戏(链接:https://www.puzzle-dominosa.com/),这个游戏跟舞蹈链天生一对(误),跟数独的规则差不多,场上所有数字都必须用到1次,用到的数字用于连接数对(任意顺序数对算同种),所有数对必须并且只出现1次,属于精确覆盖题目。

  这里就可以想怎么转化为舞蹈链了,这里数对的所有可能连接方式就是舞蹈链的行,而数对的限制以及占用数字的限制则是舞蹈链的列,一旦有一个列被使用,舞蹈链会把其他用到这个列的所有行去除,以达到快速剪枝的目的。

  在这个游戏里面,数对可以横向连接((i,j)-(i,j+1)),也可竖向连接((i,j)-(i+1,j)),这两种连接方式分别是(r-1)c种和r(c-1)种,就是说行数有大约2cr;数字一般有0-(c-1)这几个数字,可以组成c(c+1)/2种数对(这个站点的dominosa游戏列数一般比行数大1),并且所有r*c个方格必须占据,只能占据1次,这意味着列数大约有3cr/2,并且这个站点的这种谜题只有1个解答,搜索宽度不会太长,最多只有20*19的谜题,执行时间不会太长。(这个站点还可以找到一个画方格游戏shikaku,也可以用精确覆盖,但是那个游戏长宽大得不像话)

  基于这个思路,我们开始写Python代码(DLX模板是用的https://github.com/DavideCanton/DancingLinksX这个链接里面的代码,我们只是构造表而已):

  首先,构造舞蹈表的列头:

def get_names(r, c, m):
    cnt = 0
    for i in range(r):
        for j in range(c):
            yield f'L({i},{j})' # location pair
            cnt = cnt + 1
    for i in range(m+2):
        for j in range(i):
            yield f'O({j},{i-1})' # number pair
            cnt = cnt + 1
View Code

  第一个二重循环对应占用数字条件列头,第一个二重循环对应占用数对条件列头

  构造每个可能的数对对应条件约束:

def compute_row(i, j, a, b, isHorizontal, r, c):
    # H is 0 .. r*(c-1)-1
    # V is r*(c-1) .. r*(c-1)+c*(r-1)-1
    # O is r*c*2-r-c .. r*c*2-r-c+(m-1)*m//2-1
    if a < b:
        t = a
        a = b
        b = t
    row = []
    if isHorizontal:
        row.append(c*i+j)
        row.append(c*i+j+1)
    else:
        row.append(c*i+j)
        row.append(c*(i+1)+j)
    row.append(r*c+a*(a+1)//2+b)
    return row
View Code

  完成舞蹈链遍历,还要把结果按照正确的格式输出。

  为了连接字符串方便,这里声明个给单个或多个字符串赋值的函数:

def insertToStr(origin, i, j, value):
    if(origin[i][j] != '+'):
        origin[i] =  origin[i][:j] + value + origin[i][j+1:]
View Code

  为了能按照指定格式输出答案,我们这里声明一个类:

class PrintFirstSol:
    def __init__(self, r, c):
        self.r = r
        self.c = c

    def __call__(self, sol):
        maxNum = 0
        chessWidth = 0
        for v in sol.values():
            k = list(map(lambda a:int(a),v[2].split('(')[1].replace(')','').split(',')))
            maxNum = maxNum if k[0] < maxNum else k[0]
            maxNum = maxNum if k[1] < maxNum else k[1]
        while maxNum > 0:
            chessWidth += 1
            maxNum //= 10
        #print(chessWidth)
        solImg = []
        for _ in range(self.r*2+2):
            Str1 = ''
            for _ in range(self.c*(chessWidth+1)+2):
                Str1 += ' '
            solImg.append(Str1)
        
        for v in sol.values():
            v.sort()
            v[0] = list(map(lambda a:int(a),v[0].split('(')[1].replace(')','').split(',')))
            v[1] = list(map(lambda a:int(a),v[1].split('(')[1].replace(')','').split(',')))
            v[2] = v[2].split('(')[1].replace(')','').split(',')
            #print(v)
            i = v[0][0]
            j = v[0][1]
            
            if(v[0][1] != v[1][1]):
                for q in range(chessWidth):
                    insertToStr(solImg, 2*i, (1+chessWidth)*j+1+q, '-')
                    insertToStr(solImg, 2*i, (1+chessWidth)*(j+1)+1+q, '-')
                    insertToStr(solImg, 2*i+2, (1+chessWidth)*j+1+q, '-')
                    insertToStr(solImg, 2*i+2, (1+chessWidth)*(j+1)+1+q, '-')
                insertToStr(solImg, 2*i, (1+chessWidth)*(j+1), '-')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*(j+1), '-')
                insertToStr(solImg, 2*i, (1+chessWidth)*j, '+')
                insertToStr(solImg, 2*i, (1+chessWidth)*(j+2), '+')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*j, '+')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*(j+2), '+')
                solImg[2*i+1] = solImg[2*i+1][:(1+chessWidth)*j]\
                + ('|%' + str(chessWidth) + 'd')%(chess[i][j])\
                + ' ' + ('%' + str(chessWidth) + 'd|')%(chess[i][j+1])\
                + solImg[2*i+1][(1+chessWidth)*(j+2)+1:]
            else:
                for q in range(chessWidth):
                    insertToStr(solImg, 2*i, (1+chessWidth)*j+1+q, '-')
                    insertToStr(solImg, 2*i+4, (1+chessWidth)*j+1+q, '-')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*j, '|')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*(j+1), '|')
                insertToStr(solImg, 2*i, (1+chessWidth)*j, '+')
                insertToStr(solImg, 2*i, (1+chessWidth)*(j+1), '+')
                insertToStr(solImg, 2*i+4, (1+chessWidth)*j, '+')
                insertToStr(solImg, 2*i+4, (1+chessWidth)*(j+1), '+')
                solImg[2*i+1] = solImg[2*i+1][:(1+chessWidth)*j]\
                + ('|%' + str(chessWidth) + 'd|')%(chess[i][j])\
                + solImg[2*i+1][(1+chessWidth)*(j+1)+1:]
                solImg[2*i+3] = solImg[2*i+3][:(1+chessWidth)*j]\
                + ('|%' + str(chessWidth) + 'd|')%(chess[i+1][j])\
                + solImg[2*i+3][(1+chessWidth)*(j+1)+1:]
            #
        #
        
        for i in solImg:
            print(i)
        
        return True
View Code

  主要的处理步骤在main()这个函数里面:

def main():
    start = time.time()
    with open('dominosaChess.txt','r') as f:
        chessStr = f.read()
    rowStrs = chessStr.split('\n')
    rowsize = len(rowStrs)
    colSize = len(rowStrs[0].split(' '))
    maxnum = 0
    for rowStr in rowStrs:
        row = []
        for colStr in rowStr.split(' '):
            maxnum = maxnum if int(colStr) < maxnum else int(colStr)
            row.append(int(colStr))
        chess.append(row)
    #print(chess)
    d = DancingLinksMatrix(get_names(rowsize, colSize, maxnum))

    for i in range(rowsize):
        for j in range(colSize-1):
            row = compute_row(i, j, chess[i][j], chess[i][j+1], True, rowsize, colSize)
            #print(row)
            d.add_sparse_row(row, already_sorted=True)
    #
    for i in range(rowsize-1):
        for j in range(colSize):
            row = compute_row(i, j, chess[i][j], chess[i+1][j], False, rowsize, colSize)
            #print(row)
            d.add_sparse_row(row, already_sorted=True)
    d.end_add()
    
    p = PrintFirstSol(rowsize, colSize)
    AlgorithmX(d, p)()
    end = time.time()
    print('the DLX runtime is : ' + str(end-start) + 's')
View Code

  总的代码就是这样:

"""
N-queens solver using Dancing Links.
"""

from dlmatrix import DancingLinksMatrix
from alg_x import AlgorithmX
import math
import time

__author__ = 'Funcfans'
chess = []
def insertToStr(origin, i, j, value):
    if(origin[i][j] != '+'):
        origin[i] =  origin[i][:j] + value + origin[i][j+1:]

def get_names(r, c, m):
    cnt = 0
    for i in range(r):
        for j in range(c):
            yield f'L({i},{j})' # location pair
            cnt = cnt + 1
    for i in range(m+2):
        for j in range(i):
            yield f'O({j},{i-1})' # number pair
            cnt = cnt + 1
#
def compute_row(i, j, a, b, isHorizontal, r, c):
    # H is 0 .. r*(c-1)-1
    # V is r*(c-1) .. r*(c-1)+c*(r-1)-1
    # O is r*c*2-r-c .. r*c*2-r-c+(m-1)*m//2-1
    if a < b:
        t = a
        a = b
        b = t
    row = []
    if isHorizontal:
        row.append(c*i+j)
        row.append(c*i+j+1)
    else:
        row.append(c*i+j)
        row.append(c*(i+1)+j)
    row.append(r*c+a*(a+1)//2+b)
    return row

class PrintFirstSol:
    def __init__(self, r, c):
        self.r = r
        self.c = c

    def __call__(self, sol):
        maxNum = 0
        chessWidth = 0
        for v in sol.values():
            k = list(map(lambda a:int(a),v[2].split('(')[1].replace(')','').split(',')))
            maxNum = maxNum if k[0] < maxNum else k[0]
            maxNum = maxNum if k[1] < maxNum else k[1]
        while maxNum > 0:
            chessWidth += 1
            maxNum //= 10
        #print(chessWidth)
        solImg = []
        for _ in range(self.r*2+2):
            Str1 = ''
            for _ in range(self.c*(chessWidth+1)+2):
                Str1 += ' '
            solImg.append(Str1)
        
        for v in sol.values():
            v.sort()
            v[0] = list(map(lambda a:int(a),v[0].split('(')[1].replace(')','').split(',')))
            v[1] = list(map(lambda a:int(a),v[1].split('(')[1].replace(')','').split(',')))
            v[2] = v[2].split('(')[1].replace(')','').split(',')
            #print(v)
            i = v[0][0]
            j = v[0][1]
            
            if(v[0][1] != v[1][1]):
                for q in range(chessWidth):
                    insertToStr(solImg, 2*i, (1+chessWidth)*j+1+q, '-')
                    insertToStr(solImg, 2*i, (1+chessWidth)*(j+1)+1+q, '-')
                    insertToStr(solImg, 2*i+2, (1+chessWidth)*j+1+q, '-')
                    insertToStr(solImg, 2*i+2, (1+chessWidth)*(j+1)+1+q, '-')
                insertToStr(solImg, 2*i, (1+chessWidth)*(j+1), '-')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*(j+1), '-')
                insertToStr(solImg, 2*i, (1+chessWidth)*j, '+')
                insertToStr(solImg, 2*i, (1+chessWidth)*(j+2), '+')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*j, '+')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*(j+2), '+')
                solImg[2*i+1] = solImg[2*i+1][:(1+chessWidth)*j]\
                + ('|%' + str(chessWidth) + 'd')%(chess[i][j])\
                + ' ' + ('%' + str(chessWidth) + 'd|')%(chess[i][j+1])\
                + solImg[2*i+1][(1+chessWidth)*(j+2)+1:]
            else:
                for q in range(chessWidth):
                    insertToStr(solImg, 2*i, (1+chessWidth)*j+1+q, '-')
                    insertToStr(solImg, 2*i+4, (1+chessWidth)*j+1+q, '-')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*j, '|')
                insertToStr(solImg, 2*i+2, (1+chessWidth)*(j+1), '|')
                insertToStr(solImg, 2*i, (1+chessWidth)*j, '+')
                insertToStr(solImg, 2*i, (1+chessWidth)*(j+1), '+')
                insertToStr(solImg, 2*i+4, (1+chessWidth)*j, '+')
                insertToStr(solImg, 2*i+4, (1+chessWidth)*(j+1), '+')
                solImg[2*i+1] = solImg[2*i+1][:(1+chessWidth)*j]\
                + ('|%' + str(chessWidth) + 'd|')%(chess[i][j])\
                + solImg[2*i+1][(1+chessWidth)*(j+1)+1:]
                solImg[2*i+3] = solImg[2*i+3][:(1+chessWidth)*j]\
                + ('|%' + str(chessWidth) + 'd|')%(chess[i+1][j])\
                + solImg[2*i+3][(1+chessWidth)*(j+1)+1:]
            #
        #
        
        for i in solImg:
            print(i)
        
        return True

def main():
    start = time.time()
    with open('dominosaChess.txt','r') as f:
        chessStr = f.read()
    rowStrs = chessStr.split('\n')
    rowsize = len(rowStrs)
    colSize = len(rowStrs[0].split(' '))
    maxnum = 0
    for rowStr in rowStrs:
        row = []
        for colStr in rowStr.split(' '):
            maxnum = maxnum if int(colStr) < maxnum else int(colStr)
            row.append(int(colStr))
        chess.append(row)
    #print(chess)
    d = DancingLinksMatrix(get_names(rowsize, colSize, maxnum))

    for i in range(rowsize):
        for j in range(colSize-1):
            row = compute_row(i, j, chess[i][j], chess[i][j+1], True, rowsize, colSize)
            #print(row)
            d.add_sparse_row(row, already_sorted=True)
    #
    for i in range(rowsize-1):
        for j in range(colSize):
            row = compute_row(i, j, chess[i][j], chess[i+1][j], False, rowsize, colSize)
            #print(row)
            d.add_sparse_row(row, already_sorted=True)
    d.end_add()
    
    p = PrintFirstSol(rowsize, colSize)
    AlgorithmX(d, p)()
    end = time.time()
    print('the DLX runtime is : ' + str(end-start) + 's')

if __name__ == "__main__":
    main()
All Code

  这里在代码同目录下创建一个dominosaChess.txt文件,并且输入以下布局:

7 5 1 2 6 7 6 7 3
2 1 1 1 6 7 0 2 3
5 7 3 6 2 0 4 2 0
1 4 4 5 6 1 6 1 5
7 2 0 5 4 3 3 0 4
2 7 0 0 7 6 5 1 3
4 5 5 4 1 0 6 2 0
6 4 5 3 4 7 3 2 3
View Code

  可以得到以下输出:

+---+---+-+-+---+-+
|7 5|1 2|6|7|6 7|3|
+-+-+-+-+ | +-+-+ |
|2|1 1|1|6|7|0|2|3|
| +---+ +-+-+ | +-+
|5|7 3|6|2 0|4|2|0|
+-+---+-+-+-+-+-+ |
|1|4 4|5 6|1|6|1|5|
| +-+-+---+ | | +-+
|7|2|0|5 4|3|3|0|4|
+-+ | +---+-+-+-+ |
|2|7|0|0 7|6|5 1|3|
| +-+-+---+ +---+-+
|4|5 5|4 1|0|6 2|0|
+-+-+-+-+-+-+---+ |
|6 4|5 3|4 7|3 2|3|
+---+---+---+---+-+

the DLX runtime is : 0.005000591278076172s

  可以看出来,这种方法解dominosa比其他方法解要快很多。

  把答案填在dominosa里面并提交:

  大功告成!

猜你喜欢

转载自www.cnblogs.com/dgutfly/p/11950845.html
今日推荐