zoukankan      html  css  js  c++  java
  • 使用改良版多值覆盖Dancing link X (舞蹈链)求解aquarium游戏

      在上一篇文章中,我们通过改造了dancing link代码解出了aquarium游戏,并输出了正确答案。

      但是之前的代码感觉有些慢,10*10的谜面都要跑24秒,而且感觉之前的dancing link代码有些不完善(存在重复查询问题)。这一篇文章介绍如何改良多值覆盖dancing link模板代码,还有如何在整体上优化这个游戏的解题流程。

      之前的代码是从所有列中选择可能性最少的列进行突破,以减少查询宽度;但是在查询过程中发现了问题:之前查询过的较高占据值的行可能会再次被查询到,从而浪费不少时间。这里就需要在每个列的查询过程做额外处理:先从值最大的行开始消除,并且退出对这个列的遍历之前都不还原这些行。

      之前遍历序列可能是这样:[1,2,3] [1,2,4] [1,3,2] [1,3,4] [1,4,2] [1,4,3],现在的序列则是:[1,2,3] [1,2,4] [1,3,4] [2,3,4],查询次数少了一些(别小看这少掉的2次·,反应到查询树里面影响可能显著,特别是最初的几层)

      还有一些重要的优化:如果消除的行过多,导致最终能选的行总值无法满足要求,也要及时返回。为了随时随地查验总值,防止过度消除,在headerCell里面添加一个属性:xum_limit_num,并根据这个思路进行优化:

    """
    Implementation of Donald Knuth's Dancing Links Sparse Matrix
    as a circular doubly linked list. (http://arxiv.org/abs/cs/0011047)
    """
    
    import random
    import numpy as np
    
    __author__ = "FunCfans"
    
    class CannotAddRowsError(Exception):
        pass
    #
    class EmptyDLMatrix(Exception):
        pass
    #
    class Cell:
        """
        Inner cell, storing 4 pointers to neighbors, a pointer to the column header
        and the indexes associated.
        """
        __slots__ = list("UDLRC") + ["indexes", "limit_num"]
    
        def __init__(self, limitNum=1):
            self.U = self.D = self.L = self.R = self
            self.C = None
            self.indexes = None
            self.limit_num = limitNum
    
        def __str__(self):
            return f"Node: {self.indexes}"
    
        def __repr__(self):
            return f"Cell[{self.indexes}]"
    
    
    class HeaderCell(Cell):
        """
        Column Header cell, a special cell that stores also a name and a size
        member.
        """
        __slots__ = ["size", "name", "is_first", "sum_limit_num"]
    
        def __init__(self, name, limitNum=1):
            super(HeaderCell, self).__init__(limitNum)
            self.size = 0
            self.name = name
            self.is_first = False
            self.sum_limit_num = 0
    
    class DancingLinksMatrix:
        """
        Dancing Links sparse matrix implementation.
        It stores a circular doubly linked list of 1s, and another list
        of column headers. Every cell points to its upper, lower, left and right
        neighbors in a circular fashion.
        """
    
        def __init__(self, columns):
            """
            Creates a DL_Matrix.
            :param columns: it can be an integer or an iterable. If columns is an
                            integer, columns columns are added to the matrix,
                            named C0,...,CN where N = columns -1. If columns is an
                            iterable, the number of columns and the names are
                            deduced from the iterable, else TypeError is raised.
                            The iterable may yield the names, or a tuple
                            (name,primary). primary is a bool value that is True
                            if the column is a primary one. If not specified, is
                            assumed that the column is a primary one.
            :raises TypeError, if columns is not a number neither an iterable.
            """
            self.header = HeaderCell("<H>")
            self.header.is_first = True
            self.rows = self.cols = 0
            self.col_list = []
            self._create_column_headers(columns)
    
        def _create_column_headers(self, columns):
            if isinstance(columns, int):
                columns = int(columns)
                column_names = ((f"C{i}", 1) for i in range(columns))
            else:
                try:
                    column_names = iter(columns)
                except TypeError:
                    raise TypeError("Argument is not valid")
    
            prev = self.header
            # links every column in a for loop
            for name in column_names:
                primary = True
                if isinstance(name, tuple) or isinstance(name, list):
                    name, limitNum = name
                else:
                    limitNum = 1
                cell = HeaderCell(name, limitNum)
                cell.indexes = (-1, self.cols)
                cell.is_first = False
                self.col_list.append(cell)
                if primary:
                    prev.R = cell
                    cell.L = prev
                    prev = cell
                self.cols += 1
    
            prev.R = self.header
            self.header.L = prev
    
        def add_sparse_row(self, row, already_sorted=False):
            """
            Adds a sparse row to the matrix. The row is in format
            [ind_0, ..., ind_n] where 0 <= ind_i < dl_matrix.ncols.
            If called after end_add is executed, CannotAddRowsError is raised.
            :param row: a sequence of integers indicating the 1s in the row.
            :param already_sorted: True if the row is already sorted,
                                   default is False. Use it for performance
                                   optimization.
            :raises CannotAddRowsError if end_add was already called.
            """
            if self.col_list is None:
                raise CannotAddRowsError()
    
            prev = None
            start = None
    
            if not already_sorted:
                row = sorted(row)
    
            cell = None
            for ind in row:
                if isinstance(ind, int):
                    ind = (ind, 1)
                cell = Cell(ind[1])
                cell.indexes = (self.rows, ind[0])
    
                if prev:
                    prev.R = cell
                    cell.L = prev
                else:
                    start = cell
    
                col = self.col_list[ind[0]]
                # link the cell with the previous one and with the right column
                # cells.
                last = col.U
                last.D = cell
                cell.U = last
                col.U = cell
                cell.D = col
                cell.C = col
                col.size += 1
                prev = cell
                col.sum_limit_num += ind[1]
    
            start.L = cell
            cell.R = start
            self.rows += 1
    
        def end_add(self):
            """
            Called when there are no more rows to be inserted. Not strictly
            necessary, but it can save some memory.
            """
            self.col_list = None
    
        def min_column(self):
            """
            Returns the column header of the column with the minimum number of 1s.
            :return: A column header.
            :raises: EmptyDLMatrix if the matrix is empty.
            """
            # noinspection PyUnresolvedReferences
            if self.header.R.is_first:
                raise EmptyDLMatrix()
    
            col_min = self.header.R
    
            for col in iterate_cell(self.header, 'R'):
                if not col.is_first and col.size < col_min.size:
                    col_min = col
    
            return col_min
    
        def random_column(self):
            """
            Returns a random column header. (The matrix header is never returned)
            :return: A column header.
            :raises: EmptyDLMatrix if the matrix is empty.
            """
            col = self.header.R
            if col is self.header:
                raise EmptyDLMatrix()
    
            n = random.randint(0, self.cols - 1)
    
            for _ in range(n):
                col = col.R
    
            if col.is_first:
                col = col.R
            return col
    
        def __str__(self):
            names = []
            m = np.zeros((self.rows, self.cols), dtype=np.uint8)
            rows, cols = set(), []
    
            for col in iterate_cell(self.header, 'R'):
                cols.append(col.indexes[1])
                # noinspection PyUnresolvedReferences
                names.append(col.name)
    
                for cell in iterate_cell(col, 'D'):
                    ind = cell.indexes
                    rows.add(ind[0])
                    m[ind] = 1
    
            m = m[list(rows)][:, cols]
            return "
    ".join([", ".join(names), str(m)])
    
        @staticmethod
        def coverRow(r, isadd=False):
            for j in iterate_cell(r, 'R'):
                if j.C.limit_num < j.limit_num:
                    return False
            for j in iterate_cell(r, 'R'):
                j.D.U = j.U
                j.U.D = j.D
                j.C.size -= 1
                j.C.sum_limit_num -= j.limit_num
                if isadd:
                    j.C.limit_num -= j.limit_num
            return True
        
        @staticmethod
        def checkCover(c):
            subLimitNum = {}
            for i in iterate_cell(c, 'D'):
                for j in iterate_cell(i, 'R'):
                    idx = j.indexes[1]
                    if idx not in subLimitNum:
                        subLimitNum[idx] = 0
                    subLimitNum[idx] += j.limit_num
                    if j.C.sum_limit_num - subLimitNum[idx] < j.C.limit_num:
                        return False
                    
            return True
        
        @staticmethod
        def cover(c, isadd=False):
            """
            Covers the column c by removing the 1s in the column and also all
            the rows connected to them.
            :param c: The column header of the column that has to be covered.
            """
            # print("Cover column", c.name)
            c.R.L = c.L
            c.L.R = c.R
    
            for i in iterate_cell(c, 'D'):
                DancingLinksMatrix.coverRow(i, isadd)
            return True
    
        @staticmethod
        def uncoverRow(r, isadd=False):
            for j in iterate_cell(r, 'L'):
                j.C.sum_limit_num += j.limit_num
                j.C.size += 1
                j.D.U = j.U.D = j
                if isadd:
                    j.C.limit_num += j.limit_num
            return True
    
        @staticmethod
        def uncover(c, isadd=False):
            """
            Uncovers the column c by readding the 1s in the column and also all
            the rows connected to them.
            :param c: The column header of the column that has to be uncovered.
            """
            # print("Uncover column", c.name)
            for i in iterate_cell(c, 'U'):
                DancingLinksMatrix.uncoverRow(i, isadd)
    
            c.R.L = c.L.R = c
    
    
    def iterate_cell(cell, direction):
        cur = getattr(cell, direction)
        while cur is not cell:
            yield cur
            cur = getattr(cur, direction)
    
    
    # TODO to be completed
    class MatrixDisplayer:
        def __init__(self, matrix):
            dic = {}
    
            for col in iterate_cell(matrix.header, 'R'):
                dic[col.indexes] = col
    
            for col in iterate_cell(matrix.header, 'R'):
                first = col.D
                dic[first.indexes] = first
                for cell in iterate_cell(first, 'D'):
                    if cell is not col:
                        dic[cell.indexes] = cell
    
            self.dic = dic
            self.rows = matrix.rows
            self.cols = matrix.cols
    
        def print_matrix(self):
            m = {}
    
            for i in range(-1, self.rows):
                for j in range(0, self.cols):
                    cell = self.dic.get((i, j))
                    if cell:
                        if i == -1:
                            m[0, 2 * j] = cell.name+','+str(cell.limit_num)
                        else:
                            m[2 * (i + 1), 2 * j] = "X"+','+str(cell.limit_num)
    
            for i in range(-1, self.rows * 2):
                for j in range(0, self.cols * 2):
                    print(m.get((i, j), "   "), end="")
                print()
    
    
    if __name__ == "__main__":
        def from_dense(row):
            return [i for i, el in enumerate(row) if el]
    
        r = [from_dense([1, 0, 0, 1, 0, 0, 1]),
             from_dense([1, 0, 0, 1, 0, 0, 0]),
             from_dense([0, 0, 0, 1, 1, 0, 1]),
             from_dense([0, 0, 1, 0, 1, 1, 0]),
             from_dense([0, 1, 1, 0, 0, 1, 1]),
             from_dense([0, 1, 0, 0, 0, 0, 1])]
    
        d = DancingLinksMatrix("1234567")
    
        for row in r:
            d.add_sparse_row(row, already_sorted=True)
        d.end_add()
    
        p = MatrixDisplayer(d)
        p.print_matrix()
    
        # print(d.rows)
        # print(d.cols)
        # print(d)
    
        mc = d.min_column()
        # print(mc)
    
        d.cover(mc)
        # print(d)
    
        p.print_matrix()
    dlmatrix.py

      还有这个:

    """
    Implementation of Donald Knuth's Algorithm X
    (http://arxiv.org/abs/cs/0011047).
    """
    
    from dlmatrix import DancingLinksMatrix, iterate_cell, MatrixDisplayer
    import string
    
    __author__ = 'FunCfans'
    
    testRow = [0 ,62 ,63 ,25 ,37 ,52 ,32 ,54 ,40 ,17 ,34 ,19 ,24 ,13]
    
    class AlgorithmX:
        """Callable object implementing the Algorithm X."""
    
        def __init__(self, matrix, callback, choose_min=True):
            """
            Creates an Algorithm_X object that solves the problem
            encoded in matrix.
            :param matrix: The DL_Matrix instance.
            :param callback: The callback called on every solution. callback has to
                             be a function receiving a dict argument
                             {row_index: linked list of the row}, and can return a
                             bool value. The solver keeps going on until the
                             callback returns a True value.
            :param choose_min: If True, the column with the minimum number of 1s is
                               chosen at each iteration, if False a random column is
                               chosen.
            """
            self.sol_dict = {}
            self.stop = False
            self.matrix = matrix
            self.callback = callback
            self.choose_min = choose_min
            self.deduce_cnt = 0
            self.depth = 0
            self.last_matrix = None
            self.delta_file = None
    
        def __call__(self):
            """Starts the search."""
            #self.delta_file = open('delta_matrix.txt','w',encoding='utf-8')
            #self._print(self.matrix.header, 'start')
            self._search(0)
            #self.delta_file.close()
    
        def _print(self, currrow, op):
            self.deduce_cnt += 1
            f = open('step/' + 'step%04d_' % (self.deduce_cnt) + op + '_' + str(currrow.indexes) + '_depth%d.txt'%(self.depth),'w',encoding='utf-8')
            printrow = {}
            rowcontent = ''
            content = 'curr ' + op + ' row : ' + str(currrow.indexes) + '
    '
            content = ''
            for col in iterate_cell(self.matrix.header, 'R'):
                content += 'col name : '+col.name+' col limit : ' + str(col.limit_num)
                for row in iterate_cell(col, 'D'):
                    content += ' ' + str((row.indexes[0],row.limit_num)) + ','
                    printrow[row.indexes[0]] = row
                content += '
    '
            content += '
    '
            for k,v in printrow.items():
                content += 'row %d : '%(k) + str((v.limit_num, v.indexes[1])) + '-->'
                qv = v.R
                while(qv != v):
                    content += str((qv.limit_num, qv.indexes[1])) + '-->'
                    qv = qv.R
                content += str((qv.limit_num, qv.indexes[1])) + '
    '
            f.write(content)
            f.close()
            
            if self.last_matrix == None:
                self.last_matrix = {}
                for col in iterate_cell(self.matrix.header, 'R'):
                    self.last_matrix[(col.name, col.indexes[0])] = collist = set()
                    for row in iterate_cell(col, 'D'):
                        collist.add(row.indexes[0])
            else:
                curr_matrix = {}
                for col in iterate_cell(self.matrix.header, 'R'):
                    curr_matrix[(col.name, col.indexes[0])] = collist = set()
                    for row in iterate_cell(col, 'D'):
                        collist.add(row.indexes[0])
                add_col = set()
                addv = set(curr_matrix.keys()).difference(set(self.last_matrix.keys()))
                if len(addv) > 0:
                    self.delta_file.write('add_col : '+str(addv) + ' ')
                delv = set(self.last_matrix.keys()).difference(set(curr_matrix.keys()))
                if len(delv) > 0:
                    self.delta_file.write('delete_col : '+str(delv) + ' ')
                for k,v in curr_matrix.items():
                    if k not in self.last_matrix:continue
                    addv = v.difference(self.last_matrix[k])
                    if len(addv) > 0:
                        self.delta_file.write('add_col_'+str(k)+' : '+str(addv))
                    self.delta_file.write(' ')
                for k,v in self.last_matrix.items():
                    if k not in curr_matrix:continue
                    delv = self.last_matrix[k].difference(v)
                    if len(delv) > 0:
                        self.delta_file.write('delete_col_'+str(k)+' : '+str(delv))
                    self.delta_file.write(' ')
                self.delta_file.write('
    ')
                self.last_matrix = curr_matrix
            
        
        def _search(self, k):
            # print(f"Size: {k}") # k is depth
            # print(f"Solution: {self.sol_dict}")
            # print("Matrix:")
            # print(self.matrix)
    
            if self.matrix.header.R == self.matrix.header:
                # matrix is empty, solution found
                if self.callback(self._create_sol(k)):
                    self.stop = True
                return
    
            if self.choose_min:
                col = self.matrix.min_column()
            else:
                col = self.matrix.random_column()
    
            # cover column col
            #
            row = col.D
            rows = []
            for row in iterate_cell(col, 'D'):
                rows.append(row)
            rows.sort(key=lambda x:x.limit_num,reverse=True)
            self.depth += 1
            for row in rows:
                if col.limit_num < row.limit_num:continue
                if col.sum_limit_num < col.limit_num:break
                isValid = True
                for j in iterate_cell(row, 'R'):
                    if j.C.limit_num < j.limit_num:
                        isValid = False
                        break
                if not isValid:
                    continue
                self.sol_dict[k] = row
                col.sum_limit_num -= row.limit_num
                col.limit_num -= row.limit_num
                row.D.U = row.U
                row.U.D = row.D
                col.size -= 1
                if col.limit_num == 0:
                    self.matrix.cover(col)
                for j in iterate_cell(row, 'R'):
                    j.C.sum_limit_num -= j.limit_num
                    j.C.limit_num -= j.limit_num
                    j.D.U = j.U
                    j.U.D = j.D
                    j.C.size -= 1
                    if j.C.limit_num == 0:
                        self.matrix.cover(j.C)
                execYou = True
                for j in iterate_cell(self.matrix.header, 'R'):
                    if j.limit_num > j.sum_limit_num:
                        execYou = False
                        break
                if execYou:
                    self._search(k + 1)
                if self.stop:
                    return
                for j in iterate_cell(row, 'L'):
                    if j.C.limit_num == 0:
                        self.matrix.uncover(j.C)
                    j.C.limit_num += j.limit_num
                if col.limit_num == 0:
                    self.matrix.uncover(col)
                col.limit_num += row.limit_num
                del self.sol_dict[k]
                # uncover columns
    
            for row in rows[::-1]:
                for j in iterate_cell(row, 'L'):
                    j.C.size += 1
                    j.D.U = j.U.D = j
                    j.C.sum_limit_num += j.limit_num
                col.size += 1
                row.D.U = row.U.D = row
                col.sum_limit_num += row.limit_num
            self.depth -= 1
            #
    
        def _create_sol(self, k):
            # creates a solution from the inner dict
            sol = {}
            for key, row in self.sol_dict.items():
                if key >= k:
                    continue
    
                tmp_list = [row.C.name]
                tmp_list.extend(r.C.name for r in iterate_cell(row, 'R'))
                sol[row.indexes[0]] = tmp_list
    
            return sol
    #
    def main():
        from_dense = (lambda row:[i for i, el in enumerate(row) if el])
        rows = [from_dense([0, 0, 1, 0, 1, 1, 0]),
                from_dense([1, 0, 0, 1, 0, 0, 1]),
                from_dense([0, 1, 1, 0, 0, 1, 0]),
                from_dense([1, 0, 0, 1, 0, 0, 0]),
                from_dense([0, 1, 0, 0, 0, 0, 1]),
                from_dense([0, 0, 0, 1, 1, 0, 1])]
        size = max(max(rows, key=max)) + 1
        d = DancingLinksMatrix(string.ascii_uppercase[:size])
        for row in rows:
            d.add_sparse_row(row, already_sorted=True)
        AlgorithmX(d, print)()
    #
    if __name__ == "__main__":
        main()
    alg_x.py

      利用这个解上一篇文章里面提到的10*10 easy,ID为69,467的谜面,运行时间为16秒。这说明了这个优化有效果。

      但是,从原理上来讲,这个解法只是锦上添花而已。重要的是事前无效解的裁剪。

      这道题目有这俩要求:

      1.水箱内同高度的方格状态必须一致;

      2.水箱内的方格必须自底向上填满。

      就是说在指定数量条件下,还要满足这些条件。可以利用这些条件反应到单行中的所有可能性,初步排除错误解:

      1.水箱内同高度的方格状态必须一致。这意味着某些占据长度过长的水箱段必须涂上去:

      比如如下阵型:(下面水箱编号是0,0,0,0,1,1,1,2,2,3)

    图1

      可以涂上这种阵型:

    图2

      也可以这样涂:

    图3

      但是无论怎么涂,最左边的那4格都必须涂上(如果所有的阵型固定点都是白色,则亦可判定不可填涂),可以将最终阵型的对应位置填上;

      这里最左边必须涂的原理还不止是这个:这里有10格,限制数量是7,如果去掉了严格大于(10-7)=3的水箱段,其他的水箱段加起来也达不到条件。

      也有那种水箱占宽超过限制数量的:(下面水箱编号是0,0,0,0,0,1,1,1,2,2,2,2,2,3,3)

    图4

      像这种,除了最右边的1个水箱段,其他3个水箱段都要排除。

      2.水箱内的方格必须自底向上填满,这意味着某些方格可以预先确定。与横段不同,竖段无需通过暴力搜索找到必经之路

      看下栗子:(自底向上是8个0,5个1,2个2)

    图5

      如果把最下段去掉,其他段加起来也不够限制,因此下段至少需要加一些水到最底下。那要加多少格水呢,答案当然是9-(15-8)=2

      再看以下栗子:(自底向上是5个0,3个1,5个2,2个3)

    图6

      可以看出,所有段严格大于2的段,上方都要用不到的区域,比如最下段,5个方格,最上面5-2=3个是用不了的。

      初期阵型优化完毕之后再执行操作,可以减少许多不必要的搜索。

      代码如下:

    """
    dominosa solver using Dancing Links.
    """
    from functools import reduce
    from dlmatrix import DancingLinksMatrix
    from alg_x import AlgorithmX
    import math
    import time
    import copy
    deepcopy = copy.deepcopy
    __author__ = 'Funcfans'
    chess = []
    valid = []
    limits = []
    presum = []
    occupy = []
    heights = []
    currheights = []
    heightIdxs = []
    heightRange = []
    haveBlock = {}
    rowsize = 0
    maxnum = 0
    def insertToStr(origin, i, j, value):
        if(origin[i][j] != '+'):
            origin[i] =  origin[i][:j] + value + origin[i][j+1:]
    #
    def printAnswer():
        subSubImg = ' ' * rowsize
        solImg = reduce(lambda a,b:a+'
    '+b,[subSubImg] * rowsize)
        blockheight = [0] * maxnum
        startHeight = {}
        for i in range(rowsize)[::-1]:
            for j in range(rowsize):
                if valid[i][j] == -1:
                    chess[i][j] = -1
                if valid[i][j] == 1:
                    continue
                if chess[i][j] not in startHeight:
                    startHeight[chess[i][j]] = i
                if startHeight[chess[i][j]] - blockheight[chess[i][j]] + 1 > i:
                    chess[i][j] = -1
        subSubImg = '-'.join(['+'] * (rowsize+1))
        for i in range(rowsize):
            print(subSubImg)
            print('|',end='')
            for j in range(rowsize):
                if chess[i][j] == -1:
                    print(' |',end='')
                else:
                    print('*|',end='')
            print('')
        print(subSubImg)
    #
    def get_names(maxnum):
        cnt = 0
        for i in range(maxnum):
            yield f'B({i})' # block
            cnt += 1
        #
        base = maxnum
        presum.append([])
        for i,limit in enumerate(limits[0]):
            presum[0].append(base)
            if limit != 0:
                yield (f'H({i},{limit})', limit)
                base += 1
        #
        presum.append([])
        for i,limit in enumerate(limits[1]):
            presum[1].append(base)
            if limit != 0:
                yield (f'V({i},{limit})', limit)
                base += 1
    #
    def compute_row(value, maxnum):
        row = []
        row.append(value)
        for d in range(2):
            for i,li in enumerate(occupy[d][value]):
                if li == 0:continue
                if li > limits[d][i]:return None
                row.append((presum[d][i],li))
        return row
    #
    class PrintFirstSol:
        def __init__(self, r, c):
            self.r = r
            self.c = c
    
        def __call__(self, sol):
            subSubImg = ' ' * rowsize
            solImg = reduce(lambda a,b:a+'
    '+b,[subSubImg] * rowsize)
            blockheight = [0] * maxnum
            startHeight = [-1] * maxnum
            reverseBlock = {}
            for k,v in haveBlock.items():
                reverseBlock[v] = k
            for k,v in sol.items():
                v.sort()
                v[0] = 'B(' + str(reverseBlock[int(v[0].replace('B(','').replace(')',''))]) + ')'
                blockheight[heights[k][0]] = heights[k][1]
                #
            #
            for i in range(rowsize)[::-1]:
                for j in range(rowsize):
                    if valid[i][j] == 1:
                        continue
                    if valid[i][j] == -1:
                        chess[i][j] = -1
                        continue
                    if startHeight[chess[i][j]] == -1:
                        startHeight[chess[i][j]] = i
                    if startHeight[chess[i][j]] - blockheight[chess[i][j]] + 1 > i:
                        chess[i][j] = -1
            subSubImg = '-'.join(['+'] * (rowsize+1))
            for i in range(rowsize):
                print(subSubImg)
                print('|',end='')
                for j in range(rowsize):
                    if valid[i][j] == 1:
                        print('*|',end='')
                    elif chess[i][j] == -1:
                        print(' |',end='')
                    else:
                        print('*|',end='')
                print('')
            print(subSubImg)
            return True
    #
    allow = 0
    def tryHorizontal(rowNum, startPos, subValid):
        have = {}
        totalvalid = rowsize
        totallimit = limits[0][rowNum]
        for i in range(rowsize)[::-1]:
            if valid[rowNum][i] != 0:
                totalvalid -= 1
                if valid[rowNum][i] == 1:
                    totallimit -= 1
                continue
            value = chess[rowNum][i]
            if value not in have:
                have[value] = [i]
            else:
                have[value].append(i)
        items = []
        for k,v in have.items():
            if len(v) > totallimit:
                for j in v:
                    subValid[j] = -1
                totalvalid -= len(v)
            else:
                items.append((k, v))
        items.sort(key=lambda a:len(a[1]),reverse=True)
        for i in items:
            v = i[1]
            if len(v) > totallimit:
                for j in v:
                    subValid[j] = -1
                totalvalid -= len(v)
                del items[items.index(i)]
            elif len(v) > totalvalid - totallimit:
                for j in v:
                    subValid[j] = 1
                totalvalid -= len(v)
                totallimit -= len(v)
                del items[items.index(i)]
        for i in range(2**len(items)):
            tryValid = [0] * rowsize
            mylimit = 0
            for j in range(len(items)):
                if (i // (2**j)) % 2 == 1:
                    for k in items[j][1]:
                        tryValid[k] = 1
                    mylimit += len(items[j][1])
                else:
                    for k in items[j][1]:
                        tryValid[k] = -1
            if mylimit == totallimit:
                for j in range(rowsize):
                    if tryValid[j] == 0:continue
                    elif subValid[j] == -2:continue
                    elif subValid[j] == 0:subValid[j] = tryValid[j]
                    elif subValid[j] != tryValid[j]:subValid[j] = -2
        return subValid
    #
    def fill():
        isChange = False
        for i in range(rowsize):
            for checkValue in (-1,1):
                checkList = valid[i]
                if checkValue == -1:
                    youlimit = limits[0][i]
                else:
                    youlimit = rowsize - limits[0][i]
                if checkList.count(-checkValue) == youlimit:
                    for j in range(rowsize):
                        if valid[i][j] != -checkValue:
                            valid[i][j] = checkValue
                            if valid[i][j] != checkValue:
                                isChange = True
                #
                checkList = list(map(lambda q:q[i], valid))
                if checkValue == -1:
                    youlimit = limits[1][i]
                else:
                    youlimit = rowsize - limits[1][i]
                if checkList.count(-checkValue) == youlimit:
                    for j in range(rowsize):
                        if valid[j][i] != -checkValue:
                            valid[j][i] = checkValue
                            if valid[j][i] != checkValue:
                                isChange = True
                #
            #
        return isChange
    #
    def tryVertical(colNum, startPos, subValid):
        have = {}
        totalvalid = rowsize
        totallimit = limits[1][colNum]
        for i in range(rowsize)[::-1]:
            if valid[i][colNum] != 0:
                totalvalid -= 1
                if valid[i][colNum] == 1:
                    totallimit -= 1
                continue
            value = chess[i][colNum]
            if value not in have:
                have[value] = [i]
            else:
                have[value].append(i)
        for k,v in have.items():
            for i in v[totallimit:]:
                subValid[i] = -1
            if totallimit-totalvalid+len(v) > 0:
                for i in v[:totallimit-totalvalid+len(v)]:
                    subValid[i] = 1
        return subValid
    #
    def fill_up_down(blockNum, rowNum, value):
        #print('blockNum :',blockNum,'rowNum :',rowNum,'value :',value)
        queryCnts = range(rowNum+1)[::-1] if value == -1 else range(rowNum, rowsize)
        for k in queryCnts:
            isContinue = False
            for l in range(rowsize):
                if blockNum != chess[k][l]:continue
                if valid[k][l] == value:break
                else:
                    valid[k][l] = value
                    retry = True
                    if value == -1:
                        if heightRange[blockNum][0] < k:
                            heightRange[blockNum][0] = k
                    else:
                        if heightRange[blockNum][1] > k:
                            heightRange[blockNum][1] = k
    #
    def trySolve():
        retry = True
        while retry:
            retry = False
            for i in range(rowsize):
                resultSet = tryHorizontal(i, 0, [0] * rowsize)
                '''
                print('limit num :',limits[0][i])
                print('resultSet :',resultSet)
                print('hori valid :',valid[i])
                print('hori chess :',chess[i])
                '''
                for j in range(rowsize):
                    blockNum = chess[i][j]
                    if resultSet[j] in (1,-1):
                        if valid[i][j] == resultSet[j]:continue
                        fill_up_down(blockNum, i, resultSet[j])
                        retry = True
            for i in range(rowsize):
                resultSet = tryVertical(i, rowsize-1, [0] * rowsize)
                for j in range(rowsize):
                    blockNum = chess[j][i]
                    if resultSet[j] in (1,-1):
                        if valid[j][i] == resultSet[j]:continue
                        fill_up_down(blockNum, j, resultSet[j])
                        retry = True
            if not retry:retry = fill()
            #print('------')
        #
        '''
        for i in range(rowsize):
            for j in range(rowsize):
                if valid[i][j] == -1:
                    print('X',end='')
                elif valid[i][j] == 0:
                    print(' ',end='')
                else:
                    print('*',end='')
            print()
        '''
    #
    def main(fileName='aquarium69,467_3.txt'):
        currheights.clear()
        heights.clear()
        heightIdxs.clear()
        heightRange.clear()
        haveBlock.clear()
        start = time.time()
        with open(fileName,'r') as f:
            chessStr = f.read()
        rowStrs = chessStr.split('
    ')
        global rowsize, maxnum
        rowsize = len(rowStrs) - 2
        colSize = len(rowStrs[0].split(' '))
        maxnum = 0
        for rowStr in rowStrs[:-2]:
            row = []
            validRow = []
            for colStr in rowStr.split(' '):
                maxnum = maxnum if int(colStr) < maxnum else int(colStr)
                row.append(int(colStr))
                validRow.append(0)
            chess.append(row)
            valid.append(validRow)
        #print(chess)
        list(map(lambda a:int(a),rowStrs[-1].split(' ')))
        limits.append(list(map(lambda a:int(a),rowStrs[-2].split(' '))))
        limits.append(list(map(lambda a:int(a),rowStrs[-1].split(' '))))
        maxnum += 1
        for _ in range(maxnum):heightRange.append([-1,rowsize])
        occupy.append([[0 for ___ in range(rowsize)] for __ in range(maxnum)])
        occupy.append([[0 for ___ in range(rowsize)] for __ in range(maxnum)])
        rowidx = 0
        trySolve()
        isSolve = True
        for i in range(rowsize):
            for j in range(rowsize):
                if valid[i][j] == 0:
                    isSolve = False
                if valid[i][j] == 1:
                    limits[0][i] -= 1
                    limits[1][j] -= 1
        if isSolve:
            printAnswer()
            end = time.time()
            print('the DLX runtime is : ' + str(end-start) + 's')
            return
        #print(presum)
        
        startIdx = 0
        for i in range(rowsize):
            for j in range(rowsize):
                if valid[i][j] == 0:
                    if chess[i][j] not in haveBlock:
                        haveBlock[chess[i][j]] = startIdx
                        chess[i][j] = startIdx
                        startIdx += 1
                    else:
                        chess[i][j] = haveBlock[chess[i][j]]
        #print(haveBlock)
        d = DancingLinksMatrix(get_names(startIdx))
        for i in range(maxnum):
            currheights.append(0)
            heightIdxs.append([])
        for i in haveBlock.values():
            row = compute_row(i, startIdx)
            #print('row :', row)
            d.add_sparse_row(row, already_sorted=True)
            heightIdxs[i].append(rowidx)
            heights.append((i, 0))
            rowidx += 1
            currheights[i] += 1
        for i,row in list(enumerate(chess))[::-1]:
            used = set()
            for j,col in enumerate(row):
                if valid[i][j] != 0:continue
                used.add(col)
                occupy[0][col][i] += 1
                occupy[1][col][j] += 1
            for col in used:
                row = compute_row(col, maxnum)# or i <= heightRange[col][0] or i >= heightRange[col][1]
                #print('row :', row)
                heightIdxs[col].append(rowidx)
                heights.append((col, currheights[col]))
                d.add_sparse_row(row, already_sorted=True)
                rowidx += 1
                currheights[col] += 1
        #print('heightRange :', heightRange)
        #print('heightIdxs :', heightIdxs)
        print('rowidx :', rowidx)
        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('aquarium2,680,806_8.txt')
        #main('aquarium5,434,697_7.txt')
        #main('aquariumSpecial Daily  17-12-2019_9.txt')
        main()
    aquarium.py

      运行10*10 easy,ID为69,467的谜面,时间为0.005000591278076172秒,效果显著!

      运行15*15 hard,ID为5,434,697的谜面,运行结果:

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | | |*|*|*|*|*|*|*|*| | | | | |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | | | | | |*|*|*|*| | | | | |*|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | | | | | | |*|*|*|*| | | | | |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |*|*|*| | | |*|*|*|*|*|*|*|*|*|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | |*| | | |*|*| |*|*|*|*| | | |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | | |*|*|*|*|*|*|*|*|*|*|*| | |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |*| |*|*| |*|*|*|*|*|*|*|*|*| |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |*|*|*|*|*| | |*|*|*|*|*|*|*| |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |*|*| |*|*|*| |*|*|*|*|*|*|*|*|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |*|*|*|*|*|*|*|*|*| | |*|*|*|*|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |*|*|*|*|*|*|*|*|*|*|*|*| |*|*|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | | | |*| |*|*|*|*|*|*|*|*|*|*|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | | | | | | | | |*|*| | |*|*|*|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |*| | | | |*|*| |*| |*|*|*|*|*|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |*| | | | | | |*| | | | | | | |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    the DLX runtime is : 0.0890049934387207s

      把答案填入对应谜面:

    图7.只要掌握了有效的优化技巧,就算是高难谜面也解给你看

      大功告成!

  • 相关阅读:
    droid开发:如何打开一个.dcm文件作为位图?
    AndroidStudio3.0 Canary 8注解报错Annotation processors must be explicitly declared now.
    Android 异步加载神器Loader全解析
    Android实现RecyclerView的下拉刷新和上拉加载更多
    Android之ViewFlipper的简单使用
    云计算之路-阿里云上:2014年6月11日17点遇到的CPU 100%状况团队
    云计算之路-阿里云上:黑色1秒,微软的问题还是阿里云的问题?团队
    [网站公告]18:07-18:20阿里云SLB故障造成网站不能正常访问团队
    上周热点回顾(6.2-6.8)团队
    云计算之路-阿里云上:受够了OCS,改用ECS+Couchbase跑缓存团队
  • 原文地址:https://www.cnblogs.com/dgutfly/p/12055995.html
Copyright © 2011-2022 走看看