'''
思路
用十字链表构造特征,每一行代表一种选择方案 "ii, jj位置放置了数值val", 最多总共有16 * 16 * 16中选择,
每种选择对应一行 每种选择都有几类特征值
feature1 ii行jj列放置了一个数值, 特征值256种
feature2 ii行已经包含了数值val, 特征值256种
feature3 jj列已经包含了数值val, 特征值256种
feature4 ii行jj列对应的块已经包含了数值val, 特征值256种
总共1024种特征对应1024列,同一个特征应该有且仅有一个选法具备该特征,否则违反了数独的约束,问题本质就是在特征矩阵中
选择行,让所有行的特征并集是1024个1,且每一列不多不少正好一个1(为了满足数独约束),就是一个精确覆盖模型,用DLX求解
之后,将选择的行映射回填充字符的位置和字符,再将数独还原
'''
from functools import lru_cache
from typing import List
class DlxPreciseOvelap:
# max_node_num是节点池的大小,取1节点大小即可, row_num和col_num是矩阵的行数和列数
# one_points是1节点的坐标的二元组(ii, jj)的列表,行坐标和列坐标都是从1开始(注意不是从0开始)
def __init__(self, max_node_num, row_num, col_num, one_points: List):
max_node_num += col_num+1 # 加上哨兵的节点数
self.__row_num = row_num
self.__col_num = col_num
self.__pos = 0 # 当前空闲的末尾位置
# 上下左右后继的节点id
self.uu, self.dd, self.ll, self.rr = [0]*max_node_num, [0]*max_node_num, [0]*max_node_num, [0]*max_node_num
self.row, self.col = [0] * max_node_num, [0] * max_node_num # 节点的行和列
self.one_num = [0] * (self.__col_num + 1) # 列上的1计数值
self.one_points = one_points
# 十字链表状态初始化
def __boot(self):
one_points = self.one_points
one_points.sort()
# 最上面一行0号节点到col_num号节点都是哨兵, 都是链表头,不是实际1节点,0号节点下面不会接其他节点,只作为是横向的哨兵
uu, dd, ll, rr = self.uu, self.dd, self.ll, self.rr
col_num = self.__col_num
for jj in range(col_num + 1):
ll[jj] = jj - 1
rr[jj] = jj + 1
uu[jj] = jj
dd[jj] = jj
ll[0], rr[col_num] = col_num, 0
self.__pos = col_num + 1
# 逐行添加节点
i = 0
n = len(one_points)
row, col, one_num = self.row, self.col, self.one_num
while i < n:
j = i
h_node = None # 一行的第一个节点
while j < n and one_points[j][0] == one_points[i][0]:
ii, jj = one_points[j]
pos = self.__pos
row[pos], col[pos] = ii, jj
one_num[jj] += 1
uu[pos], dd[pos] = jj, dd[jj]
uu[dd[jj]] = pos
dd[jj] = pos
# 新节点总是放在头节点的右边
if h_node is None:
h_node = pos
ll[h_node], rr[h_node] = h_node, h_node
else:
ll[pos], rr[pos] = h_node, rr[h_node]
ll[rr[h_node]] = pos
rr[h_node] = pos
j += 1
self.__pos += 1
i = j
# 删除下标是c的列
def __remove_col(self, c):
uu, dd, ll, rr = self.uu, self.dd, self.ll, self.rr
one_num = self.one_num
row, col, one_num = self.row, self.col, self.one_num
# 横向把竖直方向的链表断开
ll[rr[c]] = ll[c]
rr[ll[c]] = rr[c]
node = dd[c]
while node != c:
# 把c列上数值是1的行上的节点全部在数值方向删掉,因为c这一列上的节点已经整条链都断开了,c列不用重复删了
nn = rr[node]
while nn != node:
one_num[col[nn]] -= 1
dd[uu[nn]] = dd[nn]
uu[dd[nn]] = uu[nn]
nn = rr[nn]
node = dd[node]
# 恢复下标是c的列
def __resume_col(self, c):
uu, dd, ll, rr = self.uu, self.dd, self.ll, self.rr
one_num = self.one_num
row, col, one_num = self.row, self.col, self.one_num
node = uu[c]
while node != c:
nn = ll[node]
while nn != node:
dd[uu[nn]] = nn
uu[dd[nn]] = nn
one_num[col[nn]] += 1
nn = ll[nn]
node = uu[node]
# 竖直方向的链表接回去
rr[ll[c]] = c
ll[rr[c]] = c
# dfs找第一个可行解
def __dfs1(self, path: List):
uu, dd, ll, rr = self.uu, self.dd, self.ll, self.rr
one_num = self.one_num
row, col, one_num = self.row, self.col, self.one_num
if rr[0] == 0:
return True
# 选可选的行最少的列
min_one_num = 0x7fffffff
min_col = None
c = rr[0]
while c != 0:
if one_num[c] < min_one_num:
min_one_num = one_num[c]
min_col = c
c = rr[c]
self.__remove_col(min_col)
# 枚举要在min_col列放1的行
node = dd[min_col]
while node != min_col:
# 当前选择了的行中所有数值是1的列相当于都已经在这一行放了1了,所以都删除, 不用再在这些列上枚举方案了
nn = rr[node]
while nn != node:
self.__remove_col(col[nn])
nn = rr[nn]
path.append(row[node])
if self.__dfs1(path):
return True
# 恢复
path.pop()
nn = ll[node]
while nn != node:
self.__resume_col(col[nn])
nn = ll[nn]
node = dd[node]
self.__resume_col(min_col)
return False
# 返回任意一个可行解,不保证行数最小, 返回行号的列表[row1, row2, ......]
@lru_cache(typed=False, maxsize=1) # 为了这个函数能重复调用,这里使用caches
def get_one_feasible_solution(self):
self.__boot()
ans = []
if self.__dfs1(ans):
return ans
return None
'''
特征值定义
feature1 ii行jj列放置了一个数值
feature2 ii行已经包含了数值val
feature3 jj列已经包含了数值val
feature4 ii行jj列对应的块已经包含了数值val
'''
def get_feature1_idx(ii, jj, val):
ii -= 1
jj -= 1
return ii * 16 + jj + 1
def get_feature2_idx(ii, jj, val):
ii -= 1
jj -= 1
return 256 + ii * 16 + val + 1
def get_feature3_idx(ii, jj, val):
ii -= 1
jj -= 1
return 256*2 + jj * 16 + val + 1
def get_feature4_idx(ii, jj, val):
ii -= 1
jj -= 1
block_idx = 4 * (ii // 4) + jj // 4
return 256 * 3 + block_idx * 16 + val + 1
while True:
one_point = []
cur_row = 1 # 当前填充的行号
row2solution = [(0, 0, 0)] # row2solution[row] = (ii, jj, val) 表示第row表示的操作是ii行jj列放置了数值val
for ii in range(16):
try:
line = input()
except:
break
for jj in range(16):
ch = line[jj]
if ch == '-':
left, right = 0, 15
else:
left, right = ord(line[jj]) - ord('A'), ord(line[jj]) - ord('A')
# 枚举每个位置能填写的数值
for val in range(left, right+1):
one_point.append((cur_row, get_feature1_idx(ii + 1, jj + 1, val)))
one_point.append((cur_row, get_feature2_idx(ii + 1, jj + 1, val)))
one_point.append((cur_row, get_feature3_idx(ii + 1, jj + 1, val)))
one_point.append((cur_row, get_feature4_idx(ii + 1, jj + 1, val)))
row2solution.append((ii+1, jj+1, chr(ord('A') + val)))
cur_row += 1
algo = DlxPreciseOvelap((cur_row-1) * (256*4), cur_row-1, (256*4), one_point)
ret = algo.get_one_feasible_solution()
m = [[None] * 16 for _ in range(16)]
for line_idx in ret:
ii, jj, ch = row2solution[line_idx]
ii, jj = ii - 1, jj - 1
m[ii][jj] = ch
for i in range(16):
s = ''
for ch in m[i]:
s += ch
print(s)
print()
try:
line = input()
except:
break